鏈接:https://www.zhihu.com/question/47371217/answer/105797161
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
回調,其實就是把一個函數作為參數傳遞給另一個函數;回調可以是同步的也可以是異步的;同步異步和單線程多線程沒有關系。或者說,同步也可以是單線程也可以是多線程;異步則必須是多線程或者多進程(每個進程可以是單線程)。
========== 請確保到此為止沒有任何問題,如果有問題,就先不要往下看 ===========
事件機制,必然用到回調函數,有的回調是同步的,有的回調是異步的。
如果我沒記錯的話,C語言的printf回調了相應的函數,這個過程是同步的(比如你在某個地方printf里大量數據,那么程序就會卡在這里,一直到所有的數據都print了才繼續往后走);javascript的print是異步的(不確定,記不清了)。
同步回調就是:把函數b傳遞給函數a。執行a的時候,回調了b,a要一直等到b執行完才能繼續執行;
異步回調就是:把函數b傳遞給函數a。執行a的時候,回調了b,然后a就繼續往后執行,b獨自執行。---請注意這里不要和多線程糾纏起來:線程是存在於一個進程里的,而異步回調可能是好幾個進程。
另外,鼠標點擊的event不一定是異步的但一定是非阻塞的。
比如所有的event都對應一個消息循環機制(main event loop),當我有一個event(比如鼠標點擊)時,就觸發一個消息,然后該消息就回調一個函數,而這個被回調的函數要執行完才會繼續往下走---這時就是同步的。
為什么一定是非阻塞的呢?很好理解,如果是阻塞的,那么假如某個event的回調函數需要執行1分鍾,那么在這1分鍾之內,起他event就都不能被處理了。
當然,對於非阻塞的main event loop,我們可以用單進程多線程來實現,但至於是不是必須用異步回調,這個不一定。
while(true) // main event loop ----- 一定是非阻塞的
{
為鼠標event創建一個線程---回調函數a---函數a不執行完這個線程就一直等着(同步回調)
為輸出流創建一個線程---回調函數b---函數b執行同時線程繼續往后執行(異步回調)
}
同步調用
委托的Invoke方法用來進行同步調用。同步調用也可以叫阻塞調用,它將阻塞當前線程,然后執行調用,調用完畢后再繼續向下進行。
namespace SyncInvoke { class Program { static void Main(string[] args) { Console.WriteLine("---------同步調用AsyncTest------------"); AddHandler handler = new AddHandler(PlusClass.Add); int result= handler.Invoke(2, 3); Console.WriteLine("繼續做別的事"); Console.WriteLine(result); Console.ReadKey(); } } public delegate int AddHandler(int a,int b); class PlusClass { public static int Add(int a, int b) { Console.WriteLine("開始計算" + a + "+" + b); Thread.Sleep(3000); Console.WriteLine("計算完成"); return a + b; } } }
同步調用會阻塞線程,如果是要調用一項繁重的工作(如大量IO操作),可能會讓程序停頓很長時間,造成糟糕的用戶體驗,這時候異步調用就很有必要了。
異步調用
異步調用不阻塞線程,而是把調用塞到線程池中,程序主線程或UI線程可以繼續執行。
委托的異步調用通過BeginInvoke和EndInvoke來實現。
namespace AsyncTest { class Program { static void Main(string[] args) { Console.WriteLine("-----異步調用AsyncTest------"); AddHandler handler = new AddHandler(PlusClass.Add); var result = handler.BeginInvoke(2, 3, null, null); Console.WriteLine("繼續做別的事"); for (int i = 0; i < 10; i++) { Console.WriteLine("..."); Thread.Sleep(500); } Console.WriteLine(handler.EndInvoke(result)); Console.ReadKey(); } } public delegate int AddHandler(int a,int b); class PlusClass { public static int Add(int a, int b) { Console.WriteLine("開始計算" + a + "+" + b); Thread.Sleep(3000); Console.WriteLine("計算完成"); return a + b; } } }
可以看到,主線程並沒有等待,而是直接向下運行了。
但是問題依然存在,當主線程運行到EndInvoke時,如果這時調用沒有結束(這種情況很可能出現),這時為了等待調用結果,線程依舊會被阻塞。
異步委托,也可以參考如下寫法:
action.BeginInvoke(obj,ar=>action.EndInvoke(ar),null);
簡簡單單兩句話就可以完成一部操作。