程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 探索c#之Async、Await剖析

探索c#之Async、Await剖析

編輯:C#入門知識

探索c#之Async、Await剖析


基本介紹 Async、Await是net4.x新增的異步編程方式,其目的是為了簡化異步程序編寫,和之前APM方式簡單對比如下。   APM方式,BeginGetRequestStream需要傳入回調函數,線程碰到BeginXXX時會以非阻塞形式繼續執行下面邏輯,完成後回調先前傳入的函數。       HttpWebRequest myReq =(HttpWebRequest)WebRequest.Create("http://cnblogs.com/");      myReq.BeginGetRequestStream();      //to do Async方式,使用Async標記Async1為異步方法,用Await標記GetRequestStreamAsync表示方法內需要耗時的操作。主線程碰到await時會立即返   回,繼續以非阻塞形式執行主線程下面的邏輯。當await耗時操作完成時,繼續執行Async1下面的邏輯  
static async void Async1()
    {
        HttpWebRequest myReq = (HttpWebRequest)WebRequest.Create("http://cnblogs.com/");
        await myReq.GetRequestStreamAsync();
        //to do
    }     

 

上面是net類庫實現的異步,如果要實現自己方法異步。 APM方式:       
    public delegate int MyDelegate(int x);   
        MyDelegate mathDel = new MyDelegate((a) => { return 1; });
         mathDel.BeginInvoke(1, (a) => { },null);

 

Async方式:    
static async void Async2()
    {
        await Task.Run(() => { Thread.Sleep(500); Console.WriteLine("bbb"); });
        Console.WriteLine("ccc");
    }
  Async2();
  Console.WriteLine("aaa");

 

  對比下來發現,async/await是非常簡潔優美的,需要寫的代碼量更少,更符合人們編寫習慣。  因為人的思維對線性步驟比較好理解的。   APM異步回調的執行步驟是:A邏輯->假C回調邏輯->B邏輯->真C回調邏輯,這會在一定程度造成思維的混亂,當一個項目中出現大量的異步回調   時,就會變的難以維護。 Async、Await的加入讓原先這種混亂的步驟,重新撥正了,執行步驟是:A邏輯->B邏輯->C邏輯。   基本原理剖析 作為一個程序員的自我修養,刨根問底的好奇心是非常重要的。 Async剛出來時會讓人有一頭霧水的感覺,await怎麼就直接返回了,微軟怎麼   又出一套新的異步模型。那是因為習慣了之前的APM非線性方式導致的,現在重歸線性步驟反而不好理解。 學習Async時候,可以利用已有的   APM方式去理解,以下代碼純屬虛構。 比如把Async2方法想象APM方式的Async3方法:    
static async void Async3()
    {
        var task= await Task.Run(() => { Thread.Sleep(500); Console.WriteLine("bbb"); });
       //注冊task完成後回調
        task.RegisterCompletedCallBack(() =>
        {
            Console.WriteLine("ccc");
        });
    }

 

  上面看其來就比較好理解些的,再把Async3方法想象Async4方法:    
static  void Async4()
    {
        var thread = new Thread(() =>
         {
             Thread.Sleep(500);
             Console.WriteLine("bbb");
         });
        //注冊thread完成後回調
        thread.RegisterCompletedCallBack(() =>
        {
            Console.WriteLine("ccc");
        });
        thread.Start();
    }

 

  這樣看起來就非常簡單明了,連async都去掉了,變成之前熟悉的編程習慣。雖然代碼純屬虛構,但基本思想是相通的,差別在於實現細節上面   。   內部實現剖析 作為一個程序員的自我修養,嚴謹更是不可少的態度。上面的基本思想雖然好理解了,但具體細節呢,編程是個來不得半點虛假的工作,那虛   構的代碼完全對不住看官們啊。   繼續看Async2方法,反編譯後的完整代碼如下:    View Code 發現async、await不見了,原來又是編譯器級別提供的語法糖優化,所以說async不算是全新的異步模型。 可以理解為async更多的是線性執行   步驟的一種回歸,專門用來簡化異步代碼編寫。 從反編譯後的代碼看出編譯器新生成一個繼承IAsyncStateMachine 的狀態機結構asyncd(代碼中叫<Async2>d__2,後面簡寫AsyncD),下面是   基於反編譯後的代碼來分析的。   IAsyncStateMachine最基本的狀態機接口定義:  
public interface IAsyncStateMachine
{
    void MoveNext();
    void SetStateMachine(IAsyncStateMachine stateMachine);
}

 

既然沒有了async、await語法糖的阻礙,就可以把代碼執行流程按線性順序來理解,其整個執行步驟如下:   1. 主線程調用Async2()方法 2. Async2()方法內初始化狀態機狀態為-1,啟動AsyncD 3. MoveNext方法內部開始執行,其task.run函數是把任務扔到線程池裡,返回個可等待的任務句柄。MoveNext源碼剖析:   //要執行任務的委托    Program.CS$<>9__CachedAnonymousMethodDelegate1 = new Action(Program.<Async2>b__0); //開始使用task做異步,是net4.0基於任務task的編程方式。    awaiter =Task.Run(Program.CS$<>9__CachedAnonymousMethodDelegate1).GetAwaiter(); //設置狀態為0,以便再次MoveNext直接break,執行switch後面的邏輯,典型的狀態機模式。   this.<>1__state = 0; //返回調用async2方法的線程,讓其繼續執行主線程後面的邏輯   this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter, Program.<Async2>d__2>(ref awaiter, ref this); return; 4. 這時就已經有2個線程在跑了,分別是主線程和Task.Run在跑的任務線程。   5. 執行主線程後面邏輯輸出aaa,任務線程運行完成後輸出bbb、在繼續執行任務線程後面的業務邏輯輸出ccc。  
Label_0090: 
awaiter.GetResult(); 
awaiter = new TaskAwaiter();
Console.WriteLine("ccc");

 

這裡可以理解為async把整個主線程同步邏輯,分拆成二塊。 第一塊是在主線程直接執行,第二塊是在任務線程完成後執行, 二塊中間是任務   線程在跑,其源碼中awaiter.GetResult()就是在等待任務線程完成後去執行第二塊。  從使用者角度來看執行步驟即為: 主線程A邏輯->異步任務線程B邏輯->主線程C邏輯。      
       Test();
        Console.WriteLine("A邏輯");
        static async void Test()
        {
            await Task.Run(() => { Thread.Sleep(1000); Console.WriteLine("B邏輯"); });
            Console.WriteLine("C邏輯"); 
        }

 

回過頭來對比下基本原理剖析小節中的虛構方法Async4(),發現區別在於一個是完成後回調,一個是等待完成後再執行,這也是實現異步最基   本的兩大類方式。   重點注意的地方 主線程A邏輯->異步任務線程B邏輯->主線程C邏輯。   注意:這3個步驟是有可能會使用同一個線程的,也可能會使用2個,甚至3個線程。 可以用Thread.CurrentThread.ManagedThreadId測試下得   知。    
     Async7();
     Console.WriteLine(Thread.CurrentThread.ManagedThreadId); 
    static async void Async7()
    {
        await Task.Run(() =>
        {
            Console.WriteLine(Thread.CurrentThread.ManagedThreadId); 
        });
        Console.WriteLine(Thread.CurrentThread.ManagedThreadId); 
    }

 

  正由於此,才會有言論說Async不用開線程,也有說需要開線程的,從單一方面來講都是對的,也都是錯的。 上面源碼是從簡分析的,具體   async內部會涉及到線程上下文切換,線程復用、調度等。 想深入的同學可以研究下ExecutionContextSwitcher、    SecurityContext.RestoreCurrentWI、ExecutionContext這幾個東東。   其實具體的物理線程細節可以不用太關心,知道其【主線程A邏輯->異步任務線程B邏輯->主線程C邏輯】這個基本原理即可。 另外Async也會有   線程開銷的,所以要合理分業務場景去使用。   總結 從逐漸剖析Async中發現,Net提供的異步方式基本上一脈相承的,如: 1. net4.5的Async,拋去語法糖就是Net4.0的Task+狀態機。  2. net4.0的Task, 退化到3.5即是(Thread、ThreadPool)+實現的等待、取消等API操作。   本文以async為起點,簡單剖析了其內部原理及實現,希望對大家有所幫助。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved