① 「高並發」兩種非同步模型與深度解析Future介面-
大家好,我是冰河~~
本文有點長,但是滿滿的干貨,以實際案例的形式分析了兩種非同步模型,並從源碼角度深度解析Future介面和FutureTask類,希望大家踏下心來,打開你的IDE,跟著文章看源碼,相信你一定收獲不小!
在java的並發編程中,大體上會分為兩種非同步編程模型,一類是直接以非同步的形式來並行運行其他的任務,不需要返回任務的結果數據。一類是以非同步的形式運行其他任務,需要返回結果。
1.無返回結果的非同步模型
無返回結果的非同步任務,可以直接將任務丟進線程或線程池中運行,此時,無法直接獲得任務的執行結果數據,一種方式是可以使用回調方法來獲取任務的運行結果。
具體的方案是:定義一個回調介面,並在介面中定義接收任務結果數據的方法,具體邏輯在回調介面的實現類中完成。將回調介面與任務參數一同放進線程或線程池中運行,任務運行後調用介面方法,執行回調介面實現類中的邏輯來處理結果數據。這里,給出一個簡單的示例供參考。
便於介面的通用型,這里為回調介面定義了泛型。
回調介面的實現類主要用來對任務的返回結果進行相應的業務處理,這里,為了方便演示,只是將結果數據返回。大家需要根據具體的業務場景來做相應的分析和處理。
任務的執行類是具體執行任務的類,實現Runnable介面,在此類中定義一個回調介面類型的成員變數和一個String類型的任務參數(模擬任務的參數),並在構造方法中注入回調介面和任務參數。在run方法中執行任務,任務完成後將任務的結果數據封裝成TaskResult對象,調用回調介面的方法將TaskResult對象傳遞到回調方法中。
到這里,整個大的框架算是完成了,接下來,就是測試看能否獲取到非同步任務的結果了。
在測試類中,使用Thread類創建一個新的線程,並啟動線程運行任務。運行程序最終的介面數據如下所示。
大家可以細細品味下這種獲取非同步結果的方式。這里,只是簡單的使用了Thread類來創建並啟動線程,也可以使用線程池的方式實現。大家可自行實現以線程池的方式通過回調介面獲取非同步結果。
2.有返回結果的非同步模型
盡管使用回調介面能夠獲取非同步任務的結果,但是這種方式使用起來略顯復雜。在JDK中提供了可以直接返回非同步結果的處理方案。最常用的就是使用Future介面或者其實現類FutureTask來接收任務的返回結果。
使用Future介面往往配合線程池來獲取非同步執行結果,如下所示。
運行結果如下所示。
FutureTask類既可以結合Thread類使用也可以結合線程池使用,接下來,就看下這兩種使用方式。
結合Thread類的使用示例如下所示。
運行結果如下所示。
結合線程池的使用示例如下。
運行結果如下所示。
可以看到使用Future介面或者FutureTask類來獲取非同步結果比使用回調介面獲取非同步結果簡單多了。注意:實現非同步的方式很多,這里只是用多線程舉例。
接下來,就深入分析下Future介面。
1.Future介面
Future是JDK1.5新增的非同步編程介面,其源代碼如下所示。
可以看到,在Future介面中,總共定義了5個抽象方法。接下來,就分別介紹下這5個方法的含義。
取消任務的執行,接收一個boolean類型的參數,成功取消任務,則返回true,否則返回false。當任務已經完成,已經結束或者因其他原因不能取消時,方法會返回false,表示任務取消失敗。當任務未啟動調用了此方法,並且結果返回true(取消成功),則當前任務不再運行。如果任務已經啟動,會根據當前傳遞的boolean類型的參數來決定是否中斷當前運行的線程來取消當前運行的任務。
判斷任務在完成之前是否被取消,如果在任務完成之前被取消,則返回true;否則,返回false。
這里需要注意一個細節:只有任務未啟動,或者在完成之前被取消,才會返回true,表示任務已經被成功取消。其他情況都會返回false。
判斷任務是否已經完成,如果任務正常結束、拋出異常退出、被取消,都會返回true,表示任務已經完成。
當任務完成時,直接返回任務的結果數據;當任務未完成時,等待任務完成並返回任務的結果數據。
當任務完成時,直接返回任務的結果數據;當任務未完成時,等待任務完成,並設置了超時等待時間。在超時時間內任務完成,則返回結果;否則,拋出TimeoutException異常。
2.RunnableFuture介面
Future介面有一個重要的子介面,那就是RunnableFuture介面,RunnableFuture介面不但繼承了Future介面,而且繼承了java.lang.Runnable介面,其源代碼如下所示。
這里,問一下,RunnableFuture介面中有幾個抽象方法?想好了再說!哈哈哈。。。
這個介面比較簡單run()方法就是運行任務時調用的方法。
3.FutureTask類
FutureTask類是RunnableFuture介面的一個非常重要的實現類,它實現了RunnableFuture介面、Future介面和Runnable介面的所有方法。FutureTask類的源代碼比較多,這個就不粘貼了,大家自行到java.util.concurrent下查看。
(1)FutureTask類中的變數與常量
在FutureTask類中首先定義了一個狀態變數state,這個變數使用了volatile關鍵字修飾,這里,大家只需要知道volatile關鍵字通過內存屏障和禁止重排序優化來實現線程安全,後續會單獨深度分析volatile關鍵字是如何保證線程安全的。緊接著,定義了幾個任務運行時的狀態常量,如下所示。
其中,代碼注釋中給出了幾個可能的狀態變更流程,如下所示。
接下來,定義了其他幾個成員變數,如下所示。
又看到我們所熟悉的Callable介面了,Callable介面那肯定就是用來調用call()方法執行具體任務了。
看一下WaitNode類的定義,如下所示。
可以看到,WaitNode類是FutureTask類的靜態內部類,類中定義了一個Thread成員變數和指向下一個WaitNode節點的引用。其中通過構造方法將thread變數設置為當前線程。
(2)構造方法
接下來,是FutureTask的兩個構造方法,比較簡單,如下所示。
(3)是否取消與完成方法
繼續向下看源碼,看到一個任務是否取消的方法,和一個任務是否完成的方法,如下所示。
這兩方法中,都是通過判斷任務的狀態來判定任務是否已取消和已完成的。為啥會這樣判斷呢?再次查看FutureTask類中定義的狀態常量發現,其常量的定義是有規律的,並不是隨意定義的。其中,大於或者等於CANCELLED的常量為CANCELLED、INTERRUPTING和INTERRUPTED,這三個狀態均可以表示線程已經被取消。當狀態不等於NEW時,可以表示任務已經完成。
通過這里,大家可以學到一點:以後在編碼過程中,要按照規律來定義自己使用的狀態,尤其是涉及到業務中有頻繁的狀態變更的操作,有規律的狀態可使業務處理變得事半功倍,這也是通過看別人的源碼設計能夠學到的,這里,建議大家還是多看別人寫的優秀的開源框架的源碼。
(4)取消方法
我們繼續向下看源碼,接下來,看到的是cancel(boolean)方法,如下所示。
接下來,拆解cancel(boolean)方法。在cancel(boolean)方法中,首先判斷任務的狀態和CAS的操作結果,如果任務的狀態不等於NEW或者CAS的操作返回false,則直接返回false,表示任務取消失敗。如下所示。
接下來,在try代碼塊中,首先判斷是否可以中斷當前任務所在的線程來取消任務的運行。如果可以中斷當前任務所在的線程,則以一個Thread臨時變數來指向運行任務的線程,當指向的變數不為空時,調用線程對象的interrupt()方法來中斷線程的運行,最後將線程標記為被中斷的狀態。如下所示。
這里,發現變更任務狀態使用的是UNSAFE.putOrderedInt()方法,這個方法是個什麼鬼呢?點進去看一下,如下所示。
可以看到,又是一個本地方法,嘿嘿,這里先不管它,後續文章會詳解這些方法的作用。
接下來,cancel(boolean)方法會進入finally代碼塊,如下所示。
可以看到在finallly代碼塊中調用了finishCompletion()方法,顧名思義,finishCompletion()方法表示結束任務的運行,接下來看看它是如何實現的。點到finishCompletion()方法中看一下,如下所示。
在finishCompletion()方法中,首先定義一個for循環,循環終止因子為waiters為null,在循環中,判斷CAS操作是否成功,如果成功進行if條件中的邏輯。首先,定義一個for自旋循環,在自旋循環體中,喚醒WaitNode堆棧中的線程,使其運行完成。當WaitNode堆棧中的線程運行完成後,通過break退出外層for循環。接下來調用done()方法。done()方法又是個什麼鬼呢?點進去看一下,如下所示。
可以看到,done()方法是一個空的方法體,交由子類來實現具體的業務邏輯。
當我們的具體業務中,需要在取消任務時,執行一些額外的業務邏輯,可以在子類中覆寫done()方法的實現。
(5)get()方法
繼續向下看FutureTask類的代碼,FutureTask類中實現了兩個get()方法,如下所示。
沒參數的get()方法為當任務未運行完成時,會阻塞,直到返回任務結果。有參數的get()方法為當任務未運行完成,並且等待時間超出了超時時間,會TimeoutException異常。
兩個get()方法的主要邏輯差不多,一個沒有超時設置,一個有超時設置,這里說一下主要邏輯。判斷任務的當前狀態是否小於或者等於COMPLETING,也就是說,任務是NEW狀態或者COMPLETING,調用awaitDone()方法,看下awaitDone()方法的實現,如下所示。
接下來,拆解awaitDone()方法。在awaitDone()方法中,最重要的就是for自旋循環,在循環中首先判斷當前線程是否被中斷,如果已經被中斷,則調用removeWaiter()將當前線程從堆棧中移除,並且拋出InterruptedException異常,如下所示。
接下來,判斷任務的當前狀態是否完成,如果完成,並且堆棧句柄不為空,則將堆棧中的當前線程設置為空,返回當前任務的狀態,如下所示。
當任務的狀態為COMPLETING時,使當前線程讓出CPU資源,如下所示。
如果堆棧為空,則創建堆棧對象,如下所示。
如果queued變數為false,通過CAS操作為queued賦值,如果awaitDone()方法傳遞的timed參數為true,則計算超時時間,當時間已超時,則在堆棧中移除當前線程並返回任務狀態,如下所示。如果未超時,則重置超時時間,如下所示。
如果不滿足上述的所有條件,則將當前線程設置為等待狀態,如下所示。
接下來,回到get()方法中,當awaitDone()方法返回結果,或者任務的狀態不滿足條件時,都會調用report()方法,並將當前任務的狀態傳遞到report()方法中,並返回結果,如下所示。
看來,這里還要看下report()方法啊,點進去看下report()方法的實現,如下所示。
可以看到,report()方法的實現比較簡單,首先,將outcome數據賦值給x變數,接下來,主要是判斷接收到的任務狀態,如果狀態為NORMAL,則將x強轉為泛型類型返回;當任務的狀態大於或者等於CANCELLED,也就是任務已經取消,則拋出CancellationException異常,其他情況則拋出ExecutionException異常。
至此,get()方法分析完成。注意:一定要理解get()方法的實現,因為get()方法是我們使用Future介面和FutureTask類時,使用的比較頻繁的一個方法。
(6)set()方法與setException()方法
繼續看FutureTask類的代碼,接下來看到的是set()方法與setException()方法,如下所示。
通過源碼可以看出,set()方法與setException()方法整體邏輯幾乎一樣,只是在設置任務狀態時一個將狀態設置為NORMAL,一個將狀態設置為EXCEPTIONAL。
至於finishCompletion()方法,前面已經分析過。
(7)run()方法與runAndReset()方法
接下來,就是run()方法了,run()方法的源代碼如下所示。
可以這么說,只要使用了Future和FutureTask,就必然會調用run()方法來運行任務,掌握run()方法的流程是非常有必要的。在run()方法中,如果當前狀態不是NEW,或者CAS操作返回的結果為false,則直接返回,不再執行後續邏輯,如下所示。
接下來,在try代碼塊中,將成員變數callable賦值給一個臨時變數c,判斷臨時變數不等於null,並且任務狀態為NEW,則調用Callable介面的call()方法,並接收結果數據。並將ran變數設置為true。當程序拋出異常時,將接收結果的變數設置為null,ran變數設置為false,並且調用setException()方法將任務的狀態設置為EXCEPTIONA。接下來,如果ran變數為true,則調用set()方法,如下所示。
接下來,程序會進入finally代碼塊中,如下所示。
這里,將runner設置為null,如果任務的當前狀態大於或者等於INTERRUPTING,也就是線程被中斷了。則調用()方法,接下來,看下()方法的實現。
可以看到,()方法的實現比較簡單,當任務的狀態為INTERRUPTING時,使用while()循環,條件為當前任務狀態為INTERRUPTING,將當前線程佔用的CPU資源釋放,也就是說,當任務運行完成後,釋放線程所佔用的資源。
runAndReset()方法的邏輯與run()差不多,只是runAndReset()方法會在finally代碼塊中將任務狀態重置為NEW。runAndReset()方法的源代碼如下所示,就不重復說明了。
(8)removeWaiter()方法
removeWaiter()方法中主要是使用自旋循環的方式來移除WaitNode中的線程,比較簡單,如下所示。
最後,在FutureTask類的最後,有如下代碼。
關於這些代碼的作用,會在後續深度解析CAS文章中詳細說明,這里就不再探討。
至此,關於Future介面和FutureTask類的源碼就分析完了。
好了,今天就到這兒吧,我是冰河,我們下期見~~
② IAsyncResult非同步編程
IAsyncResult 非同步設計模式通過名為 BeginOperationName 和 EndOperationName 的兩個方法來實現原同步方法的非同步調用,如 FileStream 類提供了 BeginRead 和 EndRead 方法來從文件非同步讀取位元組,它們是 Read 方法的非同步版本。
IAsyncResult介面:
補充說明:WaitHandle用來封裝等待對共享資源進行獨占訪問的操作系統特定的對象。它是一個抽象類,我們一般不直接用,而是用它的派生類:
下面了解一些詳細使用。
BeginInvoke 方法可以使用線程非同步地執行委託所指向的方法。然後通過 EndInvoke 方法獲得方法的返回值(EndInvoke 方法的返回值就是被調用方法的返回值),或是確定方法已經被成功調用。
WaitOne的第一個參數表示要等待的毫秒數,在指定時間之內,WaitOne方法將一直等待,直到非同步調用完成,並發出通知,WaitOne方法才返回true。當等待指定時間之後,非同步調用仍未完成,WaitOne方法返回false,如果指定時間為0,表示不等待,如果為-1,表示永遠等待,直到非同步調用完成。
重點需要注意BeginInvoke方法的參數傳遞方式:
前面是其委託本身的參數。 倒數第二個參數(MethodCompleted)是回調方法委託類型,他是回調方法的委託,此委託沒有返回值,有一個IAsyncResult類型的參數,當method方法執行完後,系統會自動調用MethodCompleted方法。 最後一個參數(task)需要向MethodCompleted方法中傳遞一些值,一般可以傳遞被調用方法的委託,這個值可以使用IAsyncResult.AsyncState屬性獲得。
③ 如何進行nodejs非同步編程
更新下,我之所以讓您玩一下AJAX,是希望您體驗一下非同步,並不是希望您了解AJAX這機制的實現方法,因為AJAX是一個特別典型且簡單的非同步場景,比如:
執
行某個函數 -> 執行語句A,B,C,D -> 在D語句發起非同步請求,同時向引擎注冊一個回調事件 -> 執行E,F,G
->退出函數塊 ,引擎Loop...Loop...Loop,此時非同步的請求得到了Response,之前注冊的回調被執行。
@VILIC VANE
也提到了,實際上Node.js主要是為了應對主流web
app存在大量I/O等待而CPU閑置的場景所衍生的解決方案,而在架構上,它的後端有一個底層的worker封裝,每當你有一個諸如addUser這樣
的I/O操作時,它們都會被交由worker去執行從而達到讓出盡快讓出當前函數的執行權的目的,在向引擎注冊完回調後,內部會通過事件輪詢去檢查該I
/O事件的句柄,當句柄顯示該事件操作完成後,則注冊的回調則被執行。
所以,假設有人(按題設,簡化一下場景,有且只有2個人)同時請求
addUser(A)和userList(B),B的請求會在執行完A的請求內部所有同步代碼後被執行,而哪怕worker此時仍然在進行addUser
這一 I/O操作,用戶B也並不會被引擎掛起或者等待。這就是為什麼Node.js單節點卻一樣可以擁有高負載能力的原因。
至於什麼樣的代碼是非同步的,你看看node文檔里fs模塊的使用方法就知道了,大概的形式就是如下這種。
mole.method( args [,callback] )
當然還有一種比較極端的情況,假設您使用的資料庫是山寨的,驅動是基於同步實現的,那麼B就該等多久等多久把,樹蔭底下喝杯茶,下個棋,和後面的C,D,E,F,G打個招呼唄~
我推薦您先去玩一下前端的AJAX了解一下 非同步編程方式,體驗一下非同步的「感覺」,然後看一本叫《JavaScript非同步編程》的書。
Node.js
是一款基於Event-driven的模型構建的Framework,它典型的特徵就是通過內置的事件輪詢來調度事件,通常來說node.js的資料庫驅
動都是基於非同步實現的,所以在實際情況中,A提交博客和B注冊用戶這兩個請求是可以同時由Node.js
來handle,並按照實際操作的處理事件分別調度給予瀏覽器響應。
當然,假設您在業務代碼里寫了一個耗時很久的同步代碼(比如直接寫一
個while(true)的loop,Node就死了),由於JavaScript本身單線程的限制,所以整個App就會被block住,後續的事件/程
序只有等到該段代碼執行完成之後才會被處理,這也是為什麼我們通常不建議在Node.js層做大規模計算(JS本身的計算效率太低,會導致Node吞吐量
會大大降低),而傾向由C++的拓展去實現。
④ c++中的非同步編程——future,promise
c++中創建線程很方便,例如下面
但是當我想非同步獲取線程的執行結果,就不太方便,join()並不能返回結果。
std::async,通過這個非同步介面可以很方便的獲取線程函數的執行結果。std::async會自動創建一個線程去調用線程函數,它返回一個std::future,這個future中存儲了線程函數返回的結果,當我們需要線程函數的結果時,直接從future中獲取,非常方便。
std::future提供了一種訪問非同步操作結果的機制。從字面意思來理解,它表示未來,這個名字非常貼切,因為一個非同步操作我們是不可能馬上就獲取操作結果的,只能在未來某個時候獲取,但是我們可以以同步等待的方式來獲取結果,可以通過查詢future的狀態(future_status)來獲取非同步操作的結果。future_status有三種狀態:
獲取future結果有三種方式:
通過成員函數set_value可以設置std::promise中保存的值,該值最終會被與之關聯的std::future::get讀取到。需要注意的是:set_value只能被調用一次,多次調用會拋出std::future_error異常
輸出
分桶常用,返回下界的iter
通常這么找下標
⑤ Python非同步編程--greenlet
是使用一個任務調度器和一些生成器或者協程實現協作式用戶空間多線程的一種偽並發機制,即所謂的微線程。
當創建一個 greenlet 時,它會獲得一個最初為空的堆棧;當你第一次切換到它時,它開始運行一個指定的函數,它可能會調用其他函數,切換出greenlet等。安裝 pip install greenlet。
控制台輸出:
⑥ 關於generator非同步編程的理解以及如何動手寫
關於generator非同步編程的理解以及如何動手寫一個co模塊
generator出現之前,想要實現對非同步隊列中任務的流程式控制制,大概有這么一下幾種方式:
回調函數
事件監聽
發布/訂閱
promise對象
第一種方式想必大家是最常見的,其代碼組織方式如下:
我們把函數放到run的執行器裡面,便實現了同步操作非同步代碼的過程
⑦ 什麼是同步編程、非同步編程
同步編程:傳統的同步編程是一種請求響應模型,調用一個方法,等待其響應返回。就是一個線程獲得了一個任務,然後去執行這個任務, 當這個任務執行完畢後,才能執行接下來的另外一個任務。
非同步編程:非同步編程就是要重新考慮是否需要響應的問題,也就是縮小需要響應的地方。因為越快獲得響應,就是越同步化,順序化,事務化,性能差化,非同步編程通常是通過fire and forget方式實現。
(7)非同步編程實現擴展閱讀:
在同步編程中,所有的操作都是順序執行的,比如從socket中讀取(請求),然後寫入(回應)到socket中,每一個操作都是阻塞的。
非同步編程的原則是,讓進程處理多個並發執行的上下文來模擬並行處理方式 ,非同步應用使用一個事件循環,當一個事件觸發暫停或恢復執行上下文:
只有一個上下文處於活動狀態,上下文之間進行輪替,代碼中的顯示指令告訴事件循環,哪裡可以暫停執行,這時,進程將查找其他待處理的線程進行恢復,最終,進程將回到函數暫停的地方繼續運行,從一個執行上下文移到另一個上下文稱為切換。
⑧ Python非同步編程7:非同步迭代器
迭代器:在其內部實現yield方法和next方法的對象。可迭代對象:在類內部實現一個iter方法,並返回一個迭代器。
非同步迭代器:實現了__aiter__()和__anext__()方法的對象,必須返回一個awaitable對象。async_for支持處理非同步迭代器的
__anext__()方法返回的可等待對象,直到引發一個stopAsyncIteration異常,這個改動由PEP 492引入。
非同步可迭代對象:可在async_for語句中被使用的對象,必須通過它的__aiter__()方法返回一個asynchronous_iterator(非同步迭代器). 這個改動由PEP 492引入。
示例: 不能直接寫在普通方法或者暴露在外面。必須寫在協程函數,任意協程函數均可。
⑨ Async和Await如何簡化非同步編程,幾個實例讓
同步代碼存在的問題
對於同步的代碼,大家肯定都不陌生,因為我們平常寫的代碼大部分都是同步的,然而同步代碼卻存在一個很嚴重的問題,例如我們向一個Web伺服器發出一個請求時,如果我們發出請求的代碼是同步實現的話,這時候我們的應用程序就會處於等待狀態,直到收回一個響應信息為止,然而在這個等待的狀態,對於用戶不能操作任何的UI界面以及也沒有任何的消息,如果我們試圖去操作界面時,此時我們就會看到」應用程序為響應」的信息(在應用程序的窗口旁),相信大家在平常使用桌面軟體或者訪問web的時候,肯定都遇到過這樣類似的情況的,對於這個,大家肯定會覺得看上去非常不舒服。引起這個原因正是因為代碼的實現是同步實現的,所以在沒有得到一個響應消息之前,界面就成了一個」卡死」狀態了,所以這對於用戶來說肯定是不可接受的,因為如果我要從伺服器上下載一個很大的文件時,此時我們甚至不能對窗體進行關閉的操作的。為了具體說明同步代碼存在的問題(造成界面開始),下面通過一個程序讓大家更形象地看下問題所在:
//單擊事件
privatevoidbtnClick_Click(objectsender,EventArgse)
{
this.btnClick.Enabled=false;
longlength=AccessWeb();
this.btnClick.Enabled=true;
//這里可以做一些不依賴回復的操作
OtherWork();
this.richTextBox1.Text+=String.Format(" 回復的位元組長度為:{0}. ",length);
txbMainThreadID.Text=Thread.CurrentThread.ManagedThreadId.ToString();
}
privatelongAccessWeb()
{
MemoryStreamcontent=newMemoryStream();
//對MSDN發起一個Web請求
HttpWebRequestwebRequest=WebRequest.Create("http://msdn.microsoft.com/zh-cn/")asHttpWebRequest;
if(webRequest!=null)
{
//返回回復結果
using(WebResponseresponse=webRequest.GetResponse())
{
using(StreamresponseStream=response.GetResponseStream())
{
responseStream.CopyTo(content);
}
}
}
txbAsynMethodID.Text=Thread.CurrentThread.ManagedThreadId.ToString();
returncontent.Length;
}
⑩ 什麼是非同步編程
傳統的同步編程是一種請求響應模型,調用一個方法,等待其響應返回.
非同步編程就是要重新考慮是否需要響應的問題,也就是縮小需要響應的地方。因為越快獲得響應,就是越同步化,順序化,事務化,性能差化。
非同步編程通常是通過fire and forget方式實現,發射事件後即忘記,做別的事情了,無需立即等待剛才發射的響應結果了。(發射事件的地方稱為生產者,而將在另外一個地方響應事件的處理者稱為消費者).非同步編程是一種事件驅動編程,需要完全改變思路,將「請求響應」的思路轉變到「事件驅動」思路上,是一種軟體編程思維的轉變.下面幾種你看參考一下
1、非同步編程模型 (APM) 模式(也稱為 IAsyncResult 模式),其中非同步操作要求 Begin 和 End 方法(例如,非同步寫操作的 BeginWrite 和 EndWrite)。對於新的開發工作不再建議採用此模式。
2、基於事件的非同步模式 (EAP) 需要一個具有 Async 後綴的方法,還需要一個或多個事件、事件處理程序、委託類型和 EventArg 派生的類型。EAP 是在 .NET Framework 2.0 版中引入的。對於新的開發工作不再建議採用此模式。
3、基於任務的非同步模式 (TAP),該模式使用一個方法表示非同步操作的啟動和完成。.NET Framework 4 中引入了 TAP,並且是 .NET Framework 中非同步編程的建議方法。