分享

Thead,TheadPool,Task,async,await 的前世今生

 印度阿三17 2019-06-27

接触Abp之后,发现有很多我不熟悉的,原来在.net开发中没用到的知识点,比如线程相关的Task ,async ,await等。说实话是真的不了解,就是知道是异步执行,Abp框架中很多这样用的,就模仿着去用了,直到项目上线后经常出现一些莫名奇妙的错误。如下面的错误:

System.NotSupportedException: A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.

所以特意抽出点时间把这个知识点学习一下。
这里附上一个面试场景:

面试官:说说你对JavaScript闭包的理解吧?
我:嗯,平时都是前端工程师在写JS,我们一般只管写后端代码。
面试官:你是后端程序员啊,好吧,那问问你多线程编程的问题吧。
我:一般没用到多线程。
面试官:............................. (面试结束)

听起来虽然像是个笑话,估计我去面试和这个差不多吧,哈哈,废话不说了,赶紧补起来。

本篇目录

准备工作

  • VS2013
  • 控制台项目

线程时光之旅

创建线程整体概览

static void Main(string[] args)
{
     #region 創建線程
        Console.WriteLine("我是主線程:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
        //创建Thread线程-寫法1
        var thread1 = new Thread(new ThreadStart(CreateGo));//Console # 1.0
        //启动线程
        thread1.Start();
        
        //创建Thread线程-寫法2
        var thread2 = new Thread(new ThreadStart(CreateGo));//Console # 1.0
        //启动线程
        thread2.Start();

        //创建Thread线程-寫法3
        var thread3 = new Thread(() => { new ThreadStart(CreateGo)(); });//new Thread(() => { CreateGo(); }) 
        //启动线程
        thread3.Start();

        //上面的缩写形式 new Thread(CreateGo).Start();//Console # 1.0
        Task.Factory.StartNew(CreateGo);//4.0

        Task.Run(new Action(CreateGo));//4.5
        Console.WriteLine("主線程結束:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
        #endregion
}
public static void CreateGo()
{
    Console.WriteLine("我是線程:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
}

需要注意在创建Thread实例之后,需要手动调用 Start()方法将其启动。但对于Task来说,StartNew和Run,既会创建新的线程,同时还是立即启动它。

Thread---.Net1.0

创建并启动Thread线程

运行代码:

static void Main(string[] args)
{
  #region Thread
            Console.WriteLine("我是主線程:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
            var thread = new Thread(new ThreadStart(CreateThreadGo);
            thread.Start();
            Console.WriteLine("主線程結束");
            #endregion
}
public static void CreateThreadGo()
{
    Console.WriteLine("我是子線程:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
}

运行结果:

带参数Thread线程

F12 查看ThreadStart ,是一个无参数无返回值的委托,如果要在线程中执行一个有参数的方法要如何作业呢?

namespace System.Threading
{
    // 摘要: 
    //     表示在 System.Threading.Thread 上執行的方法。
    [ComVisible(true)]
    public delegate void ThreadStart();
}

F12 查看Thread方法,可以看到Thread有四个构造函数如下:

[SecuritySafeCritical]
public Thread(ParameterizedThreadStart start);
[SecuritySafeCritical]
public Thread(ThreadStart start);
[SecuritySafeCritical]
public Thread(ParameterizedThreadStart start, int maxStackSize);
[SecuritySafeCritical]
public Thread(ThreadStart start, int maxStackSize);

F12进入ParameterizedThreadStart 里面是可以带有一个object参数的

   [ComVisible(false)]
    public delegate void ParameterizedThreadStart(object obj);

改造代码如下:

static void Main(string[] args)
{
 #region ThreadPara
    Console.WriteLine("我是主線程:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
    //沒有匿名委託之前,只能傳遞一個Object參數,寫法如下:
    var threadPara = new Thread(new ParameterizedThreadStart(CreateThreadParaGo));
    threadPara.Start("Oject參數哦");
    //有匿名委託之后,參數隨意嘍,寫法如下:
    new Thread(delegate()
    {  
        CreateThreadParaMoreGo("參數1", "參數2", "參數3");
    }).Start();
    //lambda表達式格式:  
    new Thread(() =>
    { 
        CreateThreadParaMoreGo("arg1", "arg2", "arg3");
    }).Start();
    Console.WriteLine("主線程結束");
#endregion
}

public static void CreateThreadParaGo(object para)
{
    Thread.Sleep(100);
    Console.WriteLine("我是傳遞來的參數---------{0}",para);
    Console.WriteLine("我是接收一個object參數的子線程:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
}
public static void CreateThreadParaMoreGo(string arg1,string arg2,string arg3)
{
    Thread.Sleep(100);
    Console.WriteLine("我是傳遞來的參數----------{0},{1},{2}", arg1, arg2, arg3);
    Console.WriteLine("我是接收多個參數的子線程:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
}

执行结果:

Join()

上面的执行结果中,子线程因为Thread.Sleep(100) ,所以每次都最后才打印出输出结果,那么你可能会疑问,如果我想等子线程执行完,我再执行主线程后面的代码,怎么办?
如截图变更代码如下,运行后结果:

异常处理

static void Main(string[] args)
{
    try
    {
        new Thread(CreateExGo).Start();

    }
    catch(Exception ex)
    {
        Console.WriteLine("Exception");
    }
}

运行后,不会执行Catch里面的内容,而是直接在程序中提示报错。由此可知在Thread中是没办法捕捉子线程异常。

线程池 ---.Net1.0

试想一下,如果有大量的任务需要处理,例如网站后台对于HTTP请求的处理,那是不是要对每一个请求创建一个后台线程呢?显然不合适,这会占用大量内存,如果你的代码设计了大量使用Thread,那么有可能会超过系统最大的线程数导致崩溃,而且每次创建和销毁线程也是很耗资源,那怎么办呢?线程池就是为了解决这一问题,把创建的线程存起来,形成一个线程池(里面有多个线程),当要处理任务时,若线程池中有空闲线程(前一个任务执行完成后,线程不会被回收,会被设置为空闲状态),则直接调用线程池中的线程执行(例asp.net处理机制中的Application对象),

用线程池启用线程

运行代码

#region ThreadPool
static void Main(string[] args)
{
    for(int i=0;i<10;i  )
    {
        ThreadPool.QueueUserWorkItem(x => { CreateThreadParaGo(i); });
    }
}
#endregion

执行结果:

由上面结果可以看出虽然执行了10次,但并没有创建10个线程而是创建了4个。

有Thrad中的Join功能吗?

修改代码如下:

#region ThreadPool
static void Main(string[] args)
{ 
    for (int i = 0; i < 10; i  )
    {
        ThreadPool.QueueUserWorkItem(new WaitCallback(CreateThreadParaGo),i);
    }
}
#endregion

执行结果:

先看一下waitback的定义:

一个带参数的委托,这就要求它的委托方法必须带一个object的参数了
ThreadPool静态类通过QueueUserWorkItem()方法将工作函数排入线程池,它不需要我们主动的.Start(),那么他能不能Join()了? 下图是QueueUserWorkItem返回对象,发现没有Start和Join这样的方法。

在上面有看了Thread构造函数,以及ThreadPool应用,发现都是没有返回值的委托,如果我们要想在主线程中获取子线程执行方法的返回值,该如何作业? Task隆重登场。

Task---.Net4.0

Task是.NET4.0加入的,跟线程池ThreadPool的功能类似,用Task开启新任务时,会从线程池中调用线程,而Thread每次实例化都会创建一个新的线程。

创建Task线程

运行代码

static void Main(string[] args)
{
#region Task
    //new Task 創建方式-不帶參數 ,需要手動啟動
    Task task = new Task(CreateGo);
    task.Start();
    //new Task lambda表達式創建待參數的線程,參數可以隨意哦
    Task task1 = new Task(() => CreateThreadParaGo("我是newTask 的lambda傳入的參數哦"));
    task1.Start();
    //Task.Factory創建-不帶參數,不用手動Start
    Task task2 = Task.Factory.StartNew(CreateThreadGo);
    //Task.Factory創建-帶參數,不用手動Start
    Task task3 = Task.Factory.StartNew(()=>CreateThreadParaMoreGo("factory lambda表達式參數1","factory lambda表達式參數2","factory lambda表達式參數3"));
    //Task.Run 創建線程,不用手動Start
    Task task4=Task.Run(()=>CreateGo());
    Task task5 = Task.Run(() => CreateThreadParaGo("我是TaskRun"));
#endregion
}

运行结果:

由上面代码可知,Task有三种创建线程的方式,除了Task构造函数需要手动启动外,其他两种无需调用Start手动启动且开启的是后台线程。

Wait()

上面执行结果可知task在主线程先结束,其他子线程各自运行。如果要在主线程执行之前等待子线程结束,可以使用Wait()方法,在子线程结束后继续主线程。

在上述代码中加入task1.Wait()就可以让主线程等待task1结束后再继续执行。

返回值Task<TResult>

运行代码:

static void Main(string[] args)
{
    #region Task 返回值
    Task<string> task = Task.Run(() => GetDayOfThisWeek());
    var result = task.Result;
    Console.WriteLine("今天是:{0}", result);
    var dayName = Task.Run<string>(() => { return GetDayOfThisWeek(); });
    Console.WriteLine("今天是:{0}",dayName.Result);
    #endregion
}
public static string GetDayOfThisWeek()
{
    Thread.Sleep(1000);
    return DateTime.Now.DayOfWeek.ToString();
}

运行结果:

大家可能注意到通过task.Result获取子线程的返回值,主线程是在等子线程执行完之后才打印并输出的。task.Result除了拿到返回值外,和Wait()类似。

ContinueWith接续任务

前不久主管还特意让我看了关于javascript的promise。就和ContinueWith很像。
运行代码:

#region
static void Main(string[] args)
{
Task.Run(() => { CreateGo(); })
    .ContinueWith(x => { CreateThreadParaMoreGo("接續任務21", "接續任務22", "接續任務23"); })
    .ContinueWith(x => { CreateThreadParaGo("接續任務1"); });
#endregion
}

运行结果:

执行顺序是有先后的,总是按照接续的任务顺序进行执行。不过执行线程的Id有可能是相同的,因为Task是从线程池中启动线程。

等待任务结束WaitAll/WaitAny

在 ContitueWith 的代码中执行结果中可以看出,主线程提前结束了。如果要想等到所有线程都结束在结束主线程,如何修改? 修改代码如下:

  • 如果要所有任务都完成,才完成主线程可以用Waitall();
static void Main(string[] args)
{
 #region WaitAll
    var task1=Task.Run(()=>CreateGo());
    var task2=Task.Run(()=> CreateThreadParaGo("接續任務1"));
    var task3=Task.Run(()=>CreateThreadParaMoreGo("接續任務21", "接續任務22", "接續任務23"));
    Task.WaitAll(task1,task2,task3);         
#endregion
}

运行结果:

  • 等任意一个任务结束就继续主线程用WaitAny和WhenAny
static void Main(string[] args)
{
 #region WaitAll
    var task1=Task.Run(()=>CreateGo());
    var task2=Task.Run(()=> CreateThreadParaGo("接續任務1"));
    var task3=Task.Run(()=>CreateThreadParaMoreGo("接續任務21", "接續任務22", "接續任務23"));
    Task.WaitAny(task1,task2,task3);         
#endregion
}

运行结果:

异常处理

运行代码:

static void Main(string[] args)
{
    try
    {
        var task = Task.Run(() => { CreateExGo(); });
        task.Wait(); 

        var task2 = Task.Run<string>(() => { return CreateExGoString(); });
        var name = task2.Result;
    }
    catch (Exception ex)
    {
        Console.WriteLine("Exception:"   ex.Message);
    }
}
public static void CreateExGo()
{
    throw null;
}
public static string CreateExGoString()
{
    throw new Exception("TEST");
}

下面分若干种情况讨论一下这个异常结果:

  • 运行上述代码,能够抛转出异常,不会执行task2
  • 将task.Wait()及后面代码全部屏蔽后运行,不会输出异常信息,但在代码中提示异常,此处和Thread异常还不同,Thread 跑出异常后会一直运行异常,无法正常跳出程序,而Task只会抛出一次异常。
  • 将第一个task屏蔽后,运行task2 能捕捉并提示异常,但是并不能得到task中的自定义提示异常信息。

由上面的分析可知,第一种无返回值的task需要手动调用Wait()方法,以使主线程中能够捕捉子线程异常。而又返回至的task2则不需要再次调用,用taskResult除了返回值还类似Wait()功能。 主线程无法捕捉子线程的具体异常信息,尤其是自定义异常信息。

async&await---.Net 5.0

async&await简介

async用来修饰方法,表明这个方法是异步的,声明的方法的返回类型必须为:void,Task或Task
await必须用来修饰Task或Task,一般出现在已经用async关键字修饰的异步方法中。通常情况下,async/await成对出现才有意义,

使用方法

先用一段代码看一下async和await的工作方式:

static void Main(string[] args)
{
    #region  async
    Console.WriteLine("當前是主線程:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
    Task<int> task = GetStrLengthAsync();
    Console.WriteLine("主線程繼續執行");
    Console.WriteLine("Task返回的值"   task.Result);
    #endregion
}
static async Task<int> GetStrLengthAsync()
{
    Console.WriteLine("GetStrLengthAsync方法開始執行:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
    //此处返回的<string>中的字符串类型,而不是Task<string>
    string str = await GetString();
    Console.WriteLine("GetStrLengthAsync方法執行結束");
    return str.Length;
}

static Task<string> GetString()
{
    Console.WriteLine("GetString方法開始執行:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
    return Task<string>.Run(() =>
    {
        Thread.Sleep(1000);
        Console.WriteLine("Task.run:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
        return "GetString的返回值";
    });
}

运行结果:

执行顺序如下:

  1. main方法开启一个线程开始运行程序,输出當前是主線程:Thread Id is 8
  2. 执行GetStrLengthAsync ,输出GetStrLengthAsync方法開始執行:Thread Id 8。此处可知GetStrLengthAsync 方法中并没有创建新的线程,还是主线程
  3. 遇到await 方法,此时并没有执行GetStrLengthAsync 后面的方法也没有马上返回到main函数中,而是执行到了GetString 的第一行。 输出GetString方法開始執行:Thread Id 8,由此可以判断await这里并没有开启新的线程去执行GetString方法,而是以同步的方式执行了GetString方法。
  4. 执行到GetString中的Task.Run()的时候才开启了新的后台线程,此时主线程开始继续执行,输出主線程繼續執行
  5. 因为在GetStrLengthAsync中调用了await方法,在await方法没有返回结果之前不会继续执行该方法里面的内容。但是await不会阻塞主线程,所以才会有4的输出,当await方法结束后,会继续执行GetStrLengthAsync中其他方法,此时输出GetStrLengthAsync方法執行結束
  6. 最后输出Task返回的值13 ,这时在await方法已经完毕,直接可以取回结果输出。

await不会开启新的线程,当前线程会一直运行知道遇到能开启新线程的异步方法,比如Task.Delay() 、Task.Run、Task.Factory.StartNew 去开启线程,如果不适用.net提供的Async方法,就需要自己创建Task,创建一个新线程

await 作用域

上述代码中已经看到在async的方法前面使用await进行修饰,可以让主线程等待后台线程执行完毕,与wait类似,不过await不会阻塞主线程,只会让上面的GetStrLengthAsync暂停运行,等待await结果。

运行代码:

static void Main(string[] args)
{
     Console.WriteLine("當前是主線程:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
     Test();
     Console.WriteLine("主線程繼續執行:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
}

 static async Task Test()
{
    Console.WriteLine("Test方法開始執行:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
    Task<string> task = Task.Run(() =>
    {
        Console.WriteLine("Test中Task方法開始執行:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
        Thread.Sleep(5000);
        return "Hello World";

    });
    Console.WriteLine("Test中task之後內容開始執行:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
    string str = await task;  //5 秒之后才会执行这里
    Console.WriteLine("await之後的方法:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
    Console.WriteLine(str);
}

运行结果:

await通常情况下是放在用async修饰的方法中使用,由上面的代码运行结果可知await并不是针对于async的方法,而是针对async方法所返回给我们的Task ,这也是为什么await必须返回一个Task,所以await也可以用来修饰一个task对象,告诉编译器需要等这个task执行完毕才会往下走。

不用await如何确认Task执行完毕

static void Main(string[] args)
{
Console.WriteLine("當前是主線程:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
Console.WriteLine( DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
var task = Task.Run(() =>
{
    return GetName();
});
Console.WriteLine("主線程繼續執行:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
task.GetAwaiter().OnCompleted(() =>
{
// 2 秒之后才会执行这里
var name = task.Result;
Console.WriteLine("My name is: "   name   DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));

});
}
static string GetName()
{
    Console.WriteLine("執行getname方法Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId);
    Thread.Sleep(2000); 
    Console.WriteLine("In antoher thread.....");
    return "Shirley";
}
static async Task<string> GetNameAsync()
{
    Console.WriteLine("GetNameAsync中delay之前, current thread Id is: {0}", Thread.CurrentThread.ManagedThreadId);
    await Task.Delay(2000);
    // 这里还是主线程
    Console.WriteLine("GetNameAsync中delay之後, current thread Id is: {0}", Thread.CurrentThread.ManagedThreadId);
    return await Task.Run(() =>
    {
        Console.WriteLine("'GetName' Thread Id: {0}", Thread.CurrentThread.ManagedThreadId);
        return "Shirley";
    });
}

运行结果:

发现一件有意思的事情,也不知道是不是因为线程池的原因还是我理解的哪里有问题,我理解的是await task.delay 是会开启新线程的。我把上面Main代码中的GetName换成GetNameAsync ,运行后task.delay的前后线程没有变化

我是主線程:Thread Id 10
當前是主線程:Thread Id 10
2019-06-23 17:38:44
主線程繼續執行:Thread Id 10
主線程結束
GetNameAsync中delay之前, current thread Id is: 6
GetNameAsync中delay之後, current thread Id is: 6
'GetName' Thread Id: 11
My name is: Jesse2019-06-23 17:38:46

但如果我在main中其他代码删除直接执行GetNameAsync() ,task.delay前后的线程不是同一个会有变更,如下所示:

我是主線程:Thread Id 9
當前是主線程:Thread Id 9
2019-06-23 17:37:56
GetNameAsync中delay之前, current thread Id is: 9
主線程繼續執行:Thread Id 9
主線程結束
GetNameAsync中delay之後, current thread Id is: 11
'GetName' Thread Id: 12

Task.GetAwaiter()和await Task 的区别?

将上一节内容中Main代码中的task.GetAwaiter().OnCompleted 方法整个去掉用
Console.WriteLine("My name is: " task.Result DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); 来替代,执行结果如下:

我是主線程:Thread Id 10
當前是主線程:Thread Id 10
2019-06-23 17:43:28
主線程繼續執行:Thread Id 10
GetNameAsync中delay之前, current thread Id is: 6
GetNameAsync中delay之後, current thread Id is: 6
'GetName' Thread Id: 11
My name is: Jesse2019-06-23 17:43:30
主線程結束

对比结果得到如下总结:

  • 加上await关键字之后,后面的代码会被挂起等待,直到task执行完毕有返回值的时候才会继续向下执行,这一段时间主线程会处于挂起状态。
  • GetAwaiter方法会返回一个awaitable的对象(继承了INotifyCompletion.OnCompleted方法)我们只是传递了一个委托进去,等task完成了就会执行这个委托,但是并不会影响主线程,下面的代码会立即执行。这也是为什么两段代码结果输出不一样!

不用await,Task如何让主线程挂起

static void Main(string[] args)
{
    Console.WriteLine("當前是主線程:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
    Console.WriteLine( DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
    var task = Task.Run(() =>
    {
        return GetNameAsync();
    });
    var name = task.GetAwaiter().GetResult();
    Console.WriteLine("My name is: "   task.Result   DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
      Console.WriteLine("主線程結束");
}

Task.GetAwait()方法会给我们返回一个TaskAwaiter的对象,通过调用这个对象的GetResult方法就会挂起主线程,当然也不是所有的情况都会挂起。还记得我们Task的特性么? 在一开始的时候就启动了另一个线程去执行这个Task,当我们调用它的结果的时候如果这个Task已经执行完毕,主线程是不用等待可以直接拿其结果的,如果没有执行完毕那主线程就得挂起等待了。

如下代码:

 // 这里主线程会挂起等待,直到task执行完毕我们拿到返回结果
 var name = task.GetAwaiter().GetResult();  
 // 这里不会挂起等待,因为task已经执行完了,我们可以直接拿到结果
 var nameawait = await task;  

共享数据

private static bool _isDone = false;
static void Main(string[] args)
{
      new Thread(Done).Start();
      new Thread(Done).Start();
}
static void Done()
{
    Console.WriteLine("我是線程:Thread Id {0}",Thread.CurrentThread.ManagedThreadId);
    if (!_isDone)
    {
        _isDone = true; // 第二个线程来的时候,就不会再执行了(也不是绝对的,取决于计算机的CPU数量以及当时的运行情况)
        Console.WriteLine("這裡應該只執行一次");
    }
    else
    {
        Console.WriteLine("已經 執行過");
    }
}

运行结果

线程之间可以通过static变量来共享数据。

线程安全

private static bool _isDone = false;
static void Main(string[] args)
{
      new Thread(Done).Start();
      new Thread(Done).Start();
}
static void Done()
{
    Console.WriteLine("我是線程:Thread Id {0}",Thread.CurrentThread.ManagedThreadId);
    if (!_isDone)
    {
     
        Console.WriteLine("這裡應該只執行一次");//这就从上面的代码的從後面移到前面,就會執行兩次,線程不安全

        _isDone = true; 
    }
    else
    {
        Console.WriteLine("已經 執行過");
    }
}

运行结果:

上面这种情况并不是一直发生,运行结果是我打了断点后出现的,如果不打断点执行可能就是正常的。出现的原因是第一个线程还没有来得及把_isDone设置成true,第二个线程就进来了,而这不是我们想要的结果,在多个线程下,结果不是我们的预期结果,这就是线程不安全。

在线程安全章节中,可能会出现线程不安全的情况,为了避免这种情况,可能就用到了锁。锁有很多分类:独占锁,互斥锁,以及读写锁等,下面代码演示的是独占锁。

private static bool _isDone = false;
private static object _lock = new object();
static void Main(string[] args)
{
      new Thread(DoneLock).Start();
      new Thread(DoneLock).Start();
}
static void DoneLock()
{
    Console.WriteLine("我是沒被鎖的內容,可以並列執行線程:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
    lock (_lock)
    {
        Console.WriteLine("我是鎖裡面的線程,其他線程執行完才能被調用:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
        if (!_isDone)
        {
            Console.WriteLine("我只執行一次");
            _isDone = true;
            
        }
        else
        {
            Console.WriteLine("已經執行了");
        }
    }
}

运行结果:

在上锁之后,在同一个时间内只允许一个线程访问,其他线程会被阻塞,只有等这个所被释放后其他的线程才恩给你执行被锁住的代码

上述代码并不一定总是出现相同的结果,和电脑配置有一定关系,为了更好的展示锁中代码的执行顺序,是通过打断点调试出来的结果。 锁这块的内容很多,就不在这个地方过多阐述,后面有时间要把锁这块好好的研究一下。

Semaphore信号量

Semaphore负责协调线程,可以限制对某一资源访问的线程数量,超过这个数量之后,其它的线程就得等待,只有等现在有线程释放了之后,下面的线程才能访问。这个跟锁有相似的功能,只不过不是独占的,它允许一定数量的线程同时访问。
这里对SemaphoreSlim类的用法做一个简单的例子:
运行代码:

private static SemaphoreSlim _sem = new SemaphoreSlim(3);    // 3表示最多只能有三个线程同时访问
static void Main(string[] args)
{
     for (int i = 1; i <= 5; i  ) 
     {
         new Thread(Enter).Start(i);
     }
}
static void Enter(object id)
{
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId   " 開始排隊...");
    _sem.Wait();
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId   " 開始執行!");
    Thread.Sleep(1000 * (int)id);
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId   " 執行完畢,離開!");
    _sem.Release();
}

运行结果:

由上面的结果看出,一开始的三个线程并列执行,后面的两个线程,只有当线程有执行完毕的才会开始执行。

总结


看了很多篇资料,自己也写了一些测试用的代码,也观察了运行结果,但是要想深入的了解还是需要在项目中切身去实践。
总结一下个人感受吧,可能会有很多理解不到位甚至是错误的地方,如果有误请不吝指教:

  1. 能用Task 不用 Thread ,能用async和awai就最好选用这个。
  2. 并不是用了Task就一定会提高效率。
  3. async 和await 并不会开启新线程,除非真正触发了Async的方法,比如在await方法中开启Task.Run
  4. 要用异步就全部用异步,比如async与await的使用,不要异步中穿插着同步方法
  5. 要结合具体需求,如果要使用Result或Wait的话,没有搭配async和task是可以使用的,但是搭配用时,如果是主控台或者webservice中因为起始点不能用async和task,所以依然可以使用Result或Wait方式。但是如果是在MVC或WebAPI或者Winform中就会造成死锁的问题。所以要善用async await语法糖,不过要遵循一个原则,就是从头到尾都用async await 。

云里雾里,后续结合项目再好好理解一下吧。

参考文献

这是第二篇发布出来的文章,除了自己学习之外,这篇文章也希望有懂这块知识的伙伴能看见,能给与指导,有些地方还是理解的不够好。如果有错误的地方,还请多多指教

来源:https://www./content-4-273151.html

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多