2020国产成人精品视频,性做久久久久久久久,亚洲国产成人久久综合一区,亚洲影院天堂中文av色

分享

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

 印度阿三17 2019-06-27

接觸Abp之后,發(fā)現(xiàn)有很多我不熟悉的,原來(lái)在.net開(kāi)發(fā)中沒(méi)用到的知識(shí)點(diǎn),比如線程相關(guān)的Task ,async ,await等。說(shuō)實(shí)話是真的不了解,就是知道是異步執(zhí)行,Abp框架中很多這樣用的,就模仿著去用了,直到項(xiàng)目上線后經(jīng)常出現(xiàn)一些莫名奇妙的錯(cuò)誤。如下面的錯(cuò)誤:

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.

所以特意抽出點(diǎn)時(shí)間把這個(gè)知識(shí)點(diǎn)學(xué)習(xí)一下。
這里附上一個(gè)面試場(chǎng)景:

面試官:說(shuō)說(shuō)你對(duì)JavaScript閉包的理解吧?
我:嗯,平時(shí)都是前端工程師在寫JS,我們一般只管寫后端代碼。
面試官:你是后端程序員啊,好吧,那問(wèn)問(wèn)你多線程編程的問(wèn)題吧。
我:一般沒(méi)用到多線程。
面試官:............................. (面試結(jié)束)

聽(tīng)起來(lái)雖然像是個(gè)笑話,估計(jì)我去面試和這個(gè)差不多吧,哈哈,廢話不說(shuō)了,趕緊補(bǔ)起來(lái)。

本篇目錄

準(zhǔn)備工作

  • VS2013
  • 控制臺(tái)項(xiàng)目

線程時(shí)光之旅

創(chuàng)建線程整體概覽

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

        //創(chuàng)建Thread線程-寫法3
        var thread3 = new Thread(() => { new ThreadStart(CreateGo)(); });//new Thread(() => { CreateGo(); }) 
        //啟動(dòng)線程
        thread3.Start();

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

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

需要注意在創(chuàng)建Thread實(shí)例之后,需要手動(dòng)調(diào)用 Start()方法將其啟動(dòng)。但對(duì)于Task來(lái)說(shuō),StartNew和Run,既會(huì)創(chuàng)建新的線程,同時(shí)還是立即啟動(dòng)它。

Thread---.Net1.0

創(chuàng)建并啟動(dòng)Thread線程

運(yùn)行代碼:

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("主線程結(jié)束");
            #endregion
}
public static void CreateThreadGo()
{
    Console.WriteLine("我是子線程:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
}

運(yùn)行結(jié)果:

帶參數(shù)Thread線程

F12 查看ThreadStart ,是一個(gè)無(wú)參數(shù)無(wú)返回值的委托,如果要在線程中執(zhí)行一個(gè)有參數(shù)的方法要如何作業(yè)呢?

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

F12 查看Thread方法,可以看到Thread有四個(gè)構(gòu)造函數(shù)如下:

[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進(jìn)入ParameterizedThreadStart 里面是可以帶有一個(gè)object參數(shù)的

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

改造代碼如下:

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

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

執(zhí)行結(jié)果:

Join()

上面的執(zhí)行結(jié)果中,子線程因?yàn)門hread.Sleep(100) ,所以每次都最后才打印出輸出結(jié)果,那么你可能會(huì)疑問(wèn),如果我想等子線程執(zhí)行完,我再執(zhí)行主線程后面的代碼,怎么辦?
如截圖變更代碼如下,運(yùn)行后結(jié)果:

異常處理

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

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

運(yùn)行后,不會(huì)執(zhí)行Catch里面的內(nèi)容,而是直接在程序中提示報(bào)錯(cuò)。由此可知在Thread中是沒(méi)辦法捕捉子線程異常。

線程池 ---.Net1.0

試想一下,如果有大量的任務(wù)需要處理,例如網(wǎng)站后臺(tái)對(duì)于HTTP請(qǐng)求的處理,那是不是要對(duì)每一個(gè)請(qǐng)求創(chuàng)建一個(gè)后臺(tái)線程呢?顯然不合適,這會(huì)占用大量?jī)?nèi)存,如果你的代碼設(shè)計(jì)了大量使用Thread,那么有可能會(huì)超過(guò)系統(tǒng)最大的線程數(shù)導(dǎo)致崩潰,而且每次創(chuàng)建和銷毀線程也是很耗資源,那怎么辦呢?線程池就是為了解決這一問(wèn)題,把創(chuàng)建的線程存起來(lái),形成一個(gè)線程池(里面有多個(gè)線程),當(dāng)要處理任務(wù)時(shí),若線程池中有空閑線程(前一個(gè)任務(wù)執(zhí)行完成后,線程不會(huì)被回收,會(huì)被設(shè)置為空閑狀態(tài)),則直接調(diào)用線程池中的線程執(zhí)行(例asp.net處理機(jī)制中的Application對(duì)象),

用線程池啟用線程

運(yùn)行代碼

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

執(zhí)行結(jié)果:

由上面結(jié)果可以看出雖然執(zhí)行了10次,但并沒(méi)有創(chuàng)建10個(gè)線程而是創(chuàng)建了4個(gè)。

有Thrad中的Join功能嗎?

修改代碼如下:

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

執(zhí)行結(jié)果:

先看一下waitback的定義:

一個(gè)帶參數(shù)的委托,這就要求它的委托方法必須帶一個(gè)object的參數(shù)了
ThreadPool靜態(tài)類通過(guò)QueueUserWorkItem()方法將工作函數(shù)排入線程池,它不需要我們主動(dòng)的.Start(),那么他能不能Join()了? 下圖是QueueUserWorkItem返回對(duì)象,發(fā)現(xiàn)沒(méi)有Start和Join這樣的方法。

在上面有看了Thread構(gòu)造函數(shù),以及ThreadPool應(yīng)用,發(fā)現(xiàn)都是沒(méi)有返回值的委托,如果我們要想在主線程中獲取子線程執(zhí)行方法的返回值,該如何作業(yè)? Task隆重登場(chǎng)。

Task---.Net4.0

Task是.NET4.0加入的,跟線程池ThreadPool的功能類似,用Task開(kāi)啟新任務(wù)時(shí),會(huì)從線程池中調(diào)用線程,而Thread每次實(shí)例化都會(huì)創(chuàng)建一個(gè)新的線程。

創(chuàng)建Task線程

運(yùn)行代碼

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

運(yùn)行結(jié)果:

由上面代碼可知,Task有三種創(chuàng)建線程的方式,除了Task構(gòu)造函數(shù)需要手動(dòng)啟動(dòng)外,其他兩種無(wú)需調(diào)用Start手動(dòng)啟動(dòng)且開(kāi)啟的是后臺(tái)線程。

Wait()

上面執(zhí)行結(jié)果可知task在主線程先結(jié)束,其他子線程各自運(yùn)行。如果要在主線程執(zhí)行之前等待子線程結(jié)束,可以使用Wait()方法,在子線程結(jié)束后繼續(xù)主線程。

在上述代碼中加入task1.Wait()就可以讓主線程等待task1結(jié)束后再繼續(xù)執(zhí)行。

返回值Task<TResult>

運(yùn)行代碼:

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();
}

運(yùn)行結(jié)果:

大家可能注意到通過(guò)task.Result獲取子線程的返回值,主線程是在等子線程執(zhí)行完之后才打印并輸出的。task.Result除了拿到返回值外,和Wait()類似。

ContinueWith接續(xù)任務(wù)

前不久主管還特意讓我看了關(guān)于javascript的promise。就和ContinueWith很像。
運(yùn)行代碼:

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

運(yùn)行結(jié)果:

執(zhí)行順序是有先后的,總是按照接續(xù)的任務(wù)順序進(jìn)行執(zhí)行。不過(guò)執(zhí)行線程的Id有可能是相同的,因?yàn)門ask是從線程池中啟動(dòng)線程。

等待任務(wù)結(jié)束WaitAll/WaitAny

在 ContitueWith 的代碼中執(zhí)行結(jié)果中可以看出,主線程提前結(jié)束了。如果要想等到所有線程都結(jié)束在結(jié)束主線程,如何修改? 修改代碼如下:

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

運(yùn)行結(jié)果:

  • 等任意一個(gè)任務(wù)結(jié)束就繼續(xù)主線程用WaitAny和WhenAny
static void Main(string[] args)
{
 #region WaitAll
    var task1=Task.Run(()=>CreateGo());
    var task2=Task.Run(()=> CreateThreadParaGo("接續(xù)任務(wù)1"));
    var task3=Task.Run(()=>CreateThreadParaMoreGo("接續(xù)任務(wù)21", "接續(xù)任務(wù)22", "接續(xù)任務(wù)23"));
    Task.WaitAny(task1,task2,task3);         
#endregion
}

運(yùn)行結(jié)果:

異常處理

運(yùn)行代碼:

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");
}

下面分若干種情況討論一下這個(gè)異常結(jié)果:

  • 運(yùn)行上述代碼,能夠拋轉(zhuǎn)出異常,不會(huì)執(zhí)行task2
  • 將task.Wait()及后面代碼全部屏蔽后運(yùn)行,不會(huì)輸出異常信息,但在代碼中提示異常,此處和Thread異常還不同,Thread 跑出異常后會(huì)一直運(yùn)行異常,無(wú)法正常跳出程序,而Task只會(huì)拋出一次異常。
  • 將第一個(gè)task屏蔽后,運(yùn)行task2 能捕捉并提示異常,但是并不能得到task中的自定義提示異常信息。

由上面的分析可知,第一種無(wú)返回值的task需要手動(dòng)調(diào)用Wait()方法,以使主線程中能夠捕捉子線程異常。而又返回至的task2則不需要再次調(diào)用,用taskResult除了返回值還類似Wait()功能。 主線程無(wú)法捕捉子線程的具體異常信息,尤其是自定義異常信息。

async&await---.Net 5.0

async&await簡(jiǎn)介

async用來(lái)修飾方法,表明這個(gè)方法是異步的,聲明的方法的返回類型必須為:void,Task或Task
await必須用來(lái)修飾Task或Task,一般出現(xiàn)在已經(jīng)用async關(guān)鍵字修飾的異步方法中。通常情況下,async/await成對(duì)出現(xiàn)才有意義,

使用方法

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

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

static Task<string> GetString()
{
    Console.WriteLine("GetString方法開(kāi)始執(zhí)行: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的返回值";
    });
}

運(yùn)行結(jié)果:

執(zhí)行順序如下:

  1. main方法開(kāi)啟一個(gè)線程開(kāi)始運(yùn)行程序,輸出當(dāng)前是主線程:Thread Id is 8
  2. 執(zhí)行GetStrLengthAsync ,輸出GetStrLengthAsync方法開(kāi)始執(zhí)行:Thread Id 8。此處可知GetStrLengthAsync 方法中并沒(méi)有創(chuàng)建新的線程,還是主線程
  3. 遇到await 方法,此時(shí)并沒(méi)有執(zhí)行GetStrLengthAsync 后面的方法也沒(méi)有馬上返回到main函數(shù)中,而是執(zhí)行到了GetString 的第一行。 輸出GetString方法開(kāi)始執(zhí)行:Thread Id 8,由此可以判斷await這里并沒(méi)有開(kāi)啟新的線程去執(zhí)行GetString方法,而是以同步的方式執(zhí)行了GetString方法。
  4. 執(zhí)行到GetString中的Task.Run()的時(shí)候才開(kāi)啟了新的后臺(tái)線程,此時(shí)主線程開(kāi)始繼續(xù)執(zhí)行,輸出主線程繼續(xù)執(zhí)行。
  5. 因?yàn)樵贕etStrLengthAsync中調(diào)用了await方法,在await方法沒(méi)有返回結(jié)果之前不會(huì)繼續(xù)執(zhí)行該方法里面的內(nèi)容。但是await不會(huì)阻塞主線程,所以才會(huì)有4的輸出,當(dāng)await方法結(jié)束后,會(huì)繼續(xù)執(zhí)行GetStrLengthAsync中其他方法,此時(shí)輸出GetStrLengthAsync方法執(zhí)行結(jié)束
  6. 最后輸出Task返回的值13 ,這時(shí)在await方法已經(jīng)完畢,直接可以取回結(jié)果輸出。

await不會(huì)開(kāi)啟新的線程,當(dāng)前線程會(huì)一直運(yùn)行知道遇到能開(kāi)啟新線程的異步方法,比如Task.Delay() 、Task.Run、Task.Factory.StartNew 去開(kāi)啟線程,如果不適用.net提供的Async方法,就需要自己創(chuàng)建Task,創(chuàng)建一個(gè)新線程

await 作用域

上述代碼中已經(jīng)看到在async的方法前面使用await進(jìn)行修飾,可以讓主線程等待后臺(tái)線程執(zhí)行完畢,與wait類似,不過(guò)await不會(huì)阻塞主線程,只會(huì)讓上面的GetStrLengthAsync暫停運(yùn)行,等待await結(jié)果。

運(yùn)行代碼:

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

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

    });
    Console.WriteLine("Test中task之後內(nèi)容開(kāi)始執(zhí)行:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
    string str = await task;  //5 秒之后才會(huì)執(zhí)行這里
    Console.WriteLine("await之後的方法:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
    Console.WriteLine(str);
}

運(yùn)行結(jié)果:

await通常情況下是放在用async修飾的方法中使用,由上面的代碼運(yùn)行結(jié)果可知await并不是針對(duì)于async的方法,而是針對(duì)async方法所返回給我們的Task ,這也是為什么await必須返回一個(gè)Task,所以await也可以用來(lái)修飾一個(gè)task對(duì)象,告訴編譯器需要等這個(gè)task執(zhí)行完畢才會(huì)往下走。

不用await如何確認(rèn)Task執(zhí)行完畢

static void Main(string[] args)
{
Console.WriteLine("當(dāng)前是主線程: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("主線程繼續(xù)執(zhí)行:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
task.GetAwaiter().OnCompleted(() =>
{
// 2 秒之后才會(huì)執(zhí)行這里
var name = task.Result;
Console.WriteLine("My name is: "   name   DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));

});
}
static string GetName()
{
    Console.WriteLine("執(zhí)行g(shù)etname方法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";
    });
}

運(yùn)行結(jié)果:

發(fā)現(xiàn)一件有意思的事情,也不知道是不是因?yàn)榫€程池的原因還是我理解的哪里有問(wèn)題,我理解的是await task.delay 是會(huì)開(kāi)啟新線程的。我把上面Main代碼中的GetName換成GetNameAsync ,運(yùn)行后task.delay的前后線程沒(méi)有變化

我是主線程:Thread Id 10
當(dāng)前是主線程:Thread Id 10
2019-06-23 17:38:44
主線程繼續(xù)執(zhí)行:Thread Id 10
主線程結(jié)束
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中其他代碼刪除直接執(zhí)行GetNameAsync() ,task.delay前后的線程不是同一個(gè)會(huì)有變更,如下所示:

我是主線程:Thread Id 9
當(dāng)前是主線程:Thread Id 9
2019-06-23 17:37:56
GetNameAsync中delay之前, current thread Id is: 9
主線程繼續(xù)執(zhí)行:Thread Id 9
主線程結(jié)束
GetNameAsync中delay之後, current thread Id is: 11
'GetName' Thread Id: 12

Task.GetAwaiter()和await Task 的區(qū)別?

將上一節(jié)內(nèi)容中Main代碼中的task.GetAwaiter().OnCompleted 方法整個(gè)去掉用
Console.WriteLine("My name is: " task.Result DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); 來(lái)替代,執(zhí)行結(jié)果如下:

我是主線程:Thread Id 10
當(dāng)前是主線程:Thread Id 10
2019-06-23 17:43:28
主線程繼續(xù)執(zhí)行: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
主線程結(jié)束

對(duì)比結(jié)果得到如下總結(jié):

  • 加上await關(guān)鍵字之后,后面的代碼會(huì)被掛起等待,直到task執(zhí)行完畢有返回值的時(shí)候才會(huì)繼續(xù)向下執(zhí)行,這一段時(shí)間主線程會(huì)處于掛起狀態(tài)。
  • GetAwaiter方法會(huì)返回一個(gè)awaitable的對(duì)象(繼承了INotifyCompletion.OnCompleted方法)我們只是傳遞了一個(gè)委托進(jìn)去,等task完成了就會(huì)執(zhí)行這個(gè)委托,但是并不會(huì)影響主線程,下面的代碼會(huì)立即執(zhí)行。這也是為什么兩段代碼結(jié)果輸出不一樣!

不用await,Task如何讓主線程掛起

static void Main(string[] args)
{
    Console.WriteLine("當(dāng)前是主線程: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("主線程結(jié)束");
}

Task.GetAwait()方法會(huì)給我們返回一個(gè)TaskAwaiter的對(duì)象,通過(guò)調(diào)用這個(gè)對(duì)象的GetResult方法就會(huì)掛起主線程,當(dāng)然也不是所有的情況都會(huì)掛起。還記得我們Task的特性么? 在一開(kāi)始的時(shí)候就啟動(dòng)了另一個(gè)線程去執(zhí)行這個(gè)Task,當(dāng)我們調(diào)用它的結(jié)果的時(shí)候如果這個(gè)Task已經(jīng)執(zhí)行完畢,主線程是不用等待可以直接拿其結(jié)果的,如果沒(méi)有執(zhí)行完畢那主線程就得掛起等待了。

如下代碼:

 // 這里主線程會(huì)掛起等待,直到task執(zhí)行完畢我們拿到返回結(jié)果
 var name = task.GetAwaiter().GetResult();  
 // 這里不會(huì)掛起等待,因?yàn)閠ask已經(jīng)執(zhí)行完了,我們可以直接拿到結(jié)果
 var nameawait = await task;  

共享數(shù)據(jù)

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; // 第二個(gè)線程來(lái)的時(shí)候,就不會(huì)再執(zhí)行了(也不是絕對(duì)的,取決于計(jì)算機(jī)的CPU數(shù)量以及當(dāng)時(shí)的運(yùn)行情況)
        Console.WriteLine("這裡應(yīng)該只執(zhí)行一次");
    }
    else
    {
        Console.WriteLine("已經(jīng) 執(zhí)行過(guò)");
    }
}

運(yùn)行結(jié)果

線程之間可以通過(guò)static變量來(lái)共享數(shù)據(jù)。

線程安全

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("這裡應(yīng)該只執(zhí)行一次");//這就從上面的代碼的從後面移到前面,就會(huì)執(zhí)行兩次,線程不安全

        _isDone = true; 
    }
    else
    {
        Console.WriteLine("已經(jīng) 執(zhí)行過(guò)");
    }
}

運(yùn)行結(jié)果:

上面這種情況并不是一直發(fā)生,運(yùn)行結(jié)果是我打了斷點(diǎn)后出現(xiàn)的,如果不打斷點(diǎn)執(zhí)行可能就是正常的。出現(xiàn)的原因是第一個(gè)線程還沒(méi)有來(lái)得及把_isDone設(shè)置成true,第二個(gè)線程就進(jìn)來(lái)了,而這不是我們想要的結(jié)果,在多個(gè)線程下,結(jié)果不是我們的預(yù)期結(jié)果,這就是線程不安全。

在線程安全章節(jié)中,可能會(huì)出現(xiàn)線程不安全的情況,為了避免這種情況,可能就用到了鎖。鎖有很多分類:獨(dú)占鎖,互斥鎖,以及讀寫鎖等,下面代碼演示的是獨(dú)占鎖。

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("我是沒(méi)被鎖的內(nèi)容,可以並列執(zhí)行線程:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
    lock (_lock)
    {
        Console.WriteLine("我是鎖裡面的線程,其他線程執(zhí)行完才能被調(diào)用:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
        if (!_isDone)
        {
            Console.WriteLine("我只執(zhí)行一次");
            _isDone = true;
            
        }
        else
        {
            Console.WriteLine("已經(jīng)執(zhí)行了");
        }
    }
}

運(yùn)行結(jié)果:

在上鎖之后,在同一個(gè)時(shí)間內(nèi)只允許一個(gè)線程訪問(wèn),其他線程會(huì)被阻塞,只有等這個(gè)所被釋放后其他的線程才恩給你執(zhí)行被鎖住的代碼

上述代碼并不一定總是出現(xiàn)相同的結(jié)果,和電腦配置有一定關(guān)系,為了更好的展示鎖中代碼的執(zhí)行順序,是通過(guò)打斷點(diǎn)調(diào)試出來(lái)的結(jié)果。 鎖這塊的內(nèi)容很多,就不在這個(gè)地方過(guò)多闡述,后面有時(shí)間要把鎖這塊好好的研究一下。

Semaphore信號(hào)量

Semaphore負(fù)責(zé)協(xié)調(diào)線程,可以限制對(duì)某一資源訪問(wèn)的線程數(shù)量,超過(guò)這個(gè)數(shù)量之后,其它的線程就得等待,只有等現(xiàn)在有線程釋放了之后,下面的線程才能訪問(wèn)。這個(gè)跟鎖有相似的功能,只不過(guò)不是獨(dú)占的,它允許一定數(shù)量的線程同時(shí)訪問(wèn)。
這里對(duì)SemaphoreSlim類的用法做一個(gè)簡(jiǎn)單的例子:
運(yùn)行代碼:

private static SemaphoreSlim _sem = new SemaphoreSlim(3);    // 3表示最多只能有三個(gè)線程同時(shí)訪問(wèn)
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   " 開(kāi)始排隊(duì)...");
    _sem.Wait();
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId   " 開(kāi)始執(zhí)行!");
    Thread.Sleep(1000 * (int)id);
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId   " 執(zhí)行完畢,離開(kāi)!");
    _sem.Release();
}

運(yùn)行結(jié)果:

由上面的結(jié)果看出,一開(kāi)始的三個(gè)線程并列執(zhí)行,后面的兩個(gè)線程,只有當(dāng)線程有執(zhí)行完畢的才會(huì)開(kāi)始執(zhí)行。

總結(jié)


看了很多篇資料,自己也寫了一些測(cè)試用的代碼,也觀察了運(yùn)行結(jié)果,但是要想深入的了解還是需要在項(xiàng)目中切身去實(shí)踐。
總結(jié)一下個(gè)人感受吧,可能會(huì)有很多理解不到位甚至是錯(cuò)誤的地方,如果有誤請(qǐng)不吝指教:

  1. 能用Task 不用 Thread ,能用async和awai就最好選用這個(gè)。
  2. 并不是用了Task就一定會(huì)提高效率。
  3. async 和await 并不會(huì)開(kāi)啟新線程,除非真正觸發(fā)了Async的方法,比如在await方法中開(kāi)啟Task.Run
  4. 要用異步就全部用異步,比如async與await的使用,不要異步中穿插著同步方法
  5. 要結(jié)合具體需求,如果要使用Result或Wait的話,沒(méi)有搭配async和task是可以使用的,但是搭配用時(shí),如果是主控臺(tái)或者webservice中因?yàn)槠鹗键c(diǎn)不能用async和task,所以依然可以使用Result或Wait方式。但是如果是在MVC或WebAPI或者Winform中就會(huì)造成死鎖的問(wèn)題。所以要善用async await語(yǔ)法糖,不過(guò)要遵循一個(gè)原則,就是從頭到尾都用async await 。

云里霧里,后續(xù)結(jié)合項(xiàng)目再好好理解一下吧。

參考文獻(xiàn)

這是第二篇發(fā)布出來(lái)的文章,除了自己學(xué)習(xí)之外,這篇文章也希望有懂這塊知識(shí)的伙伴能看見(jiàn),能給與指導(dǎo),有些地方還是理解的不夠好。如果有錯(cuò)誤的地方,還請(qǐng)多多指教

來(lái)源:https://www./content-4-273151.html

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多