C# Await


每次提到異步我都選擇繞開,感覺深不可測,最近打算看看異步,但又不願意看書,網上找了幾個視頻看,發現傳智播客的老師講異步都不是很深入,關鍵的問題一筆帶過,倒是把我弄糊塗了,印象最深刻的是那個老師說的一句話:“在異步函數中,Await之后會自動創建出一個線程”。確實,在控制台程序中是這樣,但是在winform中卻不都在UI線程中執行,感覺內部隱藏不可告人的秘密,索性自己探索一下。

控制台程序:

        static void Main(string[] args)
        {
            Console.WriteLine("1.同步ThreaID:" + Thread.CurrentThread.ManagedThreadId);
            RunTimeAsync();
            Console.WriteLine("4.異步執行完畢ThreaID:" + Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(5000);
            Console.WriteLine("5.同步延遲完畢ThreaID:" + Thread.CurrentThread.ManagedThreadId);
            Console.ReadKey();
        }
        public async static Task RunTimeAsync()
        {
            Console.WriteLine("2.進入異步ThreaID:" + Thread.CurrentThread.ManagedThreadId);
            await Task.Delay(1000);
            Console.WriteLine("3.異步執行后ThreaID:" + Thread.CurrentThread.ManagedThreadId);
        }

執行結果:

同樣的代碼在winform中執行:

winform程序:

        private void button1_Click(object sender, EventArgs e)
        {
            Console.WriteLine("1.同步ThreaID:" + Thread.CurrentThread.ManagedThreadId);
            RunTimeAsync();
            Console.WriteLine("4.異步執行完畢ThreaID:" + Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(2000);
            Console.WriteLine("5.同步延遲完畢ThreaID:" + Thread.CurrentThread.ManagedThreadId);

        }
        public async static Task RunTimeAsync()
        {
            Console.WriteLine("2.進入異步ThreaID:" + Thread.CurrentThread.ManagedThreadId);
            await Task.Delay(1000);
            Console.WriteLine("3.異步執行后ThreaID:" + Thread.CurrentThread.ManagedThreadId);
        }

執行結果:

前后代碼執行結果對比后發現,

1.線程數量,winfrom都是在一個線程里執行,控制台程序是兩個線程執行,其中異步操作在新的線程里。

2.執行順序,winform先執行click中同步操作,再執行異步操作,控制台卻相反。

所以await這個關鍵字到底對異步操作動了什么手腳呢?

在查了一些相關資料后有兩個詞比較高頻:SynchronizationContext 和TaskScheduler,執行代碼時,Task不會自己執行,而是將執行內容放到SynchronizationContext 和TaskScheduler執行。

其實await Task.Delay(1000)或者await AsyncFun()(異步操作)等價於以下代碼:

Task t = AsyncFun();
var currentContext = SynchronizationContext.Current;
if (null  == currentContext)
{
    t.ContinueWith((te) => { Run(); }, TaskScheduler.Current);
}
else
{
    t.ContinueWith((te) => { Run(); }, TaskScheduler.FromCurrentSynchronizationContext())

}

如果SynchronizationContext為null就在TaskScheduler中執行,如果不為null則在FromCurrentSynchronizationContext中執行。

先了解一下TaskScheduler--------CSDN中注解是:表示一個處理將任務排隊到線程中的低級工作的對象。

看一下TaskScheduler的元數據:

TaskScheduler其實就是將Task放到Threadpool執行,通過入隊出隊的方式排列執行順序。

 根據等價代碼我們要區分一下控制台程序與winfrom程序異步操作的執行方式,

我們執行這條代碼:var currentContext = SynchronizationContext.Current;

在控制台程序中currentContext 為null

在winform中currentContext 為System.Windows.Forms.WindowsFormsSynchronizationContext

那么控制台程序是在TaskScheduler.Current所在的線程池執行,我們看到控制台的await 異步操作在其他線程中執行一致。

下面分析一下winform的執行方式:

WindowsFormsSynchronizationContext繼承自SynchronizationContext,

https://www.cnblogs.com/lzxianren/p/SynchronizationContext.html   參考了這篇文章

里面有兩點:

public virtual void Send(SendOrPostCallback d, Object state)

{

  d(state);

}

public virtual void Post(SendOrPostCallback d, Object state)

{

ThreadPool.QueueUserWorkItem(new WaitCallback(d), state);

}

在UI線程執行是,會將執行的任務用Send方法放到消息泵中,當時UI中同步執行的操作執行完畢,再執行詳細泵中的數據,這就是為什么UI中只在一個線程中執行,而且總是在最后執行。

總結:用 await 語句等待一個任務完成,當該方法在 await 處暫停時,就可以捕捉上下文(context)。如果當前 SynchronizationContext 不為空,這個上下文就是當前SynchronizationContext;如果當前 SynchronizationContext 為空,則這個上下文為當前askScheduler,該方法會在這個上下文中繼續運行。一般來說,運行 UI 線程時采用 UI 上下文,處理 ASP.NET 請求時采用 ASP.NET 請求上下文,其他很多情況下則采用線程池上下文。因此,在上面的代碼中,每個同步程序塊會試圖在原始的上下文中恢復運行。如果在 UI線程中調用 ,這個方法的每個同步程序塊都將在此 UI 線程上運行。但是,如果在線程池線程中調用,每個同步程序塊將在線程池線程上運行。 

控制台程序以及類庫是在TaskScheduler中調度執行,UI程序則是在SynchronizationContext調度執行。

參考了:https://blog.csdn.net/wlk1229/article/details/81287374   

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM