1、前言
幾年前,一個開發同學遇到同步呼叫非同步函式出現死鎖問題,導致UI介面假死。我解釋了一堆,關於狀態機、執行緒池、WindowsFormsSynchronizationContext.Post、control.BeginInvoke、APC、IOCP,結果我也沒講明白、他也沒聽明白。後來路過他座位時看到他在各種摸索、嘗試,使用Task、await、async各種組合,當時的場景是這樣的:
。問題有點複雜,隨著那個開發同學離職轉做產品後,就不了了之了。工作中許多同事對於同步、非同步也不是特別瞭解,我會以執行流程圖表加原始碼的形式表述,希望通過這篇文章最少能讓大家瞭解.NET的async await出現deadlock的原因,最好能粗略瞭解async狀態機機制、.NET在不同平臺網路呼叫實現機制。如果文章中表述存在問題,歡迎指正。
2、場景再現、執行過程解析
Winform死鎖場景
如下程式碼,如果點選按鈕觸發btn_realDead_Click事件,Ui執行緒將掛起在DeadTask().Result陷入死鎖。
死鎖產生的原因: Ui執行緒阻塞等待Task完成,Task需要通過Ui執行緒設定完成結果。
private void btn_realDead_Click(object sender, EventArgs e)
{
var result = DeadTask().Result; // UI執行緒掛起位置
PrintInfo(result);
}
/// <summary>
///
/// </summary>
/// <returns></returns>
private async Task<string> DeadTask()
{
await Task.Delay(500);
return await Task.FromResult("Hello world");
}
場景模擬,解析WindowsFormsSynchronizationContext.Post執行過程
Demo程式碼地址 : https://gitee.com/RiverBied/async-demo
死鎖模擬程式碼
使用async關鍵字將會由編譯器生成狀態機程式碼,反編譯的程式碼也不太直觀,所以我先使用非async程式碼進行簡化模擬,async程式碼下文在解析。
死鎖產生的原因: Ui執行緒阻塞等待Task完成,Task需要通過Ui執行緒設定完成結果。
解除死鎖: 通過其他執行緒設定Task完成結果,Ui執行緒等到Task完成訊號繼續執行,死鎖得到解除。
點選模擬死鎖後,輸出資訊:
執行過程
相信大家看完下面這個圖,會有更直觀認識。可以看到CurrentSynchronizationContext.Post的SendOrPostCallback內容被包裝為ThreadMethodEntry寫入到窗體的佇列物件的_threadCallbackList。但是 _threadCallbackList什麼觸發的,採用的是User32 MessageW非同步訊息介面,最後在UI執行緒空閒時系統觸發窗體回撥函式WndProc。
CurrentSynchronizationContext=WindowsFormsSynchronizationContext
WindowsFormsSynchronizationContext設定程式碼:
// 示例程式碼
public Form1()
{
InitializeComponent();
CurrentSynchronizationContext = SynchronizationContext.Current;
var controlToSendToField = typeof(WindowsFormsSynchronizationContext).GetField("controlToSendTo", BindingFlags.Instance | BindingFlags.NonPublic);
// controlToSendTo設定為當前視窗物件,讓重寫的WndProc執行接收到訊息
controlToSendToField.SetValue(CurrentSynchronizationContext, this);
}
WindowsFormsSynchronizationContext.Post原始碼:
SynchronizationContext.Post功能為傳送一個非同步委託訊息,不阻塞當前執行緒,委託訊息需要在SynchronizationContext繫結執行緒進行執行。在死鎖模擬場景中SynchronizationContext繫結的為Ui執行緒,所以委託訊息需要在Ui執行緒進行執行。
//原始碼地址: //https://github.com/dotnet/winforms/blob/release/5.0/src/System.Windows.Forms/src/System/Windows/Forms/WindowsFormsSynchronizationContext.cs#L90
public override void Post(SendOrPostCallback d, object state)
{
// 呼叫form1視窗物件的BeginInvoke
controlToSendTo?.BeginInvoke(d, new object[] { state });
}
Control.BeginInvoke
BeginInvoke關鍵原始碼:
// 定義保證在整個系統中唯一的視窗訊息,訊息值可用於傳送或釋出訊息,返回視窗訊息標識(int)。
s_threadCallbackMessage = User32.RegisterWindowMessageW(Application.WindowMessagesVersion + "_ThreadCallbackMessage");
// 將回撥函式執行資訊新增到回撥函式佇列,回撥函式即為WindowsFormsSynchronizationContext.Post的SendOrPostCallback引數,_threadCallbackList為Control欄位
_threadCallbackList.Enqueue(tme);
// 在與建立指定視窗的執行緒關聯的訊息佇列中放置(釋出)一條訊息,並在不等待執行緒處理訊息的情況下返回
User32.PostMessageW(this, s_threadCallbackMessage);
BeginInvoke原始碼:
//原始碼地址:
//https://github.com/dotnet/winforms/blob/release/5.0/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs#L4678
private object MarshaledInvoke(Control caller, Delegate method, object[] args, bool synchronous)
{
if (!IsHandleCreated)
{
throw new InvalidOperationException(SR.ErrorNoMarshalingThread);
}
ActiveXImpl activeXImpl = (ActiveXImpl)Properties.GetObject(s_activeXImplProperty);
// We don't want to wait if we're on the same thread, or else we'll deadlock.
// It is important that syncSameThread always be false for asynchronous calls.
bool syncSameThread = false;
if (User32.GetWindowThreadProcessId(this, out _) == Kernel32.GetCurrentThreadId())
{
if (synchronous)
{
syncSameThread = true;
}
}
ExecutionContext executionContext = null;
if (!syncSameThread)
{
executionContext = ExecutionContext.Capture();
}
ThreadMethodEntry tme = new ThreadMethodEntry(caller, this, method, args, synchronous, executionContext);
lock (this)
{
if (_threadCallbackList is null)
{
_threadCallbackList = new Queue();
}
}
lock (_threadCallbackList)
{
if (s_threadCallbackMessage == User32.WM.NULL)
{
// 註冊訊息返回訊息標識(int)
s_threadCallbackMessage = User32.RegisterWindowMessageW(Application.WindowMessagesVersion + "_ThreadCallbackMessage");
}
// 將回撥函式執行資訊新增到回撥函式佇列
_threadCallbackList.Enqueue(tme);
}
// 同一個執行緒則直接執行
if (syncSameThread)
{
InvokeMarshaledCallbacks();
}
else
{
// 將一個訊息放入(寄送)到與指定視窗建立的執行緒相聯絡訊息佇列裡
User32.PostMessageW(this, s_threadCallbackMessage);
}
if (synchronous)
{
if (!tme.IsCompleted)
{
WaitForWaitHandle(tme.AsyncWaitHandle);
}
if (tme._exception != null)
{
throw tme._exception;
}
return tme._retVal;
}
else
{
return tme;
}
}
WndProc
應用程式中定義的回撥函式,用於處理髮送到視窗的訊息。
示例中的程式碼:
/// <summary>
/// 重寫接收視窗的訊息的回撥函式
/// </summary>
/// <param name="m"></param>
protected override void WndProc(ref Message m)
{
if (m.Msg == GetThreadCallbackMessage())
{
var threadCallbackList = GetThreadCallbackList();
PrintInfo($"觸發WndProc:msg={m.Msg},threadCallbackList.Count={threadCallbackList.Count}");
base.WndProc(ref m);
}
else
{
base.WndProc(ref m);
}
}
/// <summary>
/// 獲取需要在Ui執行緒執行的回撥委託佇列
/// </summary>
/// <returns></returns>
private System.Collections.Queue GetThreadCallbackList()
{
var threadCallbackListFiled = typeof(Control).GetField("_threadCallbackList", BindingFlags.NonPublic | BindingFlags.Instance);
return (System.Collections.Queue)threadCallbackListFiled.GetValue(this);
}
private static int _threadCallbackMessage = 0;
/// <summary>
/// 獲取觸發回撥委託的視窗訊息標識
/// </summary>
/// <returns></returns>
private int GetThreadCallbackMessage()
{
if (_threadCallbackMessage == 0)
{
var threadCallbackMessageFiled = typeof(Control).GetField("s_threadCallbackMessage", BindingFlags.NonPublic | BindingFlags.Static);
_threadCallbackMessage = Convert.ToInt32(threadCallbackMessageFiled.GetValue(null));
}
return _threadCallbackMessage;
}
WndProc原始碼:
WndProc接收到s_threadCallbackMessage訊息觸發執行佇列_threadCallbackList的訊息。
//原始碼地址:
//https://github.com/dotnet/winforms/blob/release/5.0/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs#L12681
/// <summary>
/// Base wndProc. All messages are sent to wndProc after getting filtered
/// through the preProcessMessage function. Inheriting controls should
/// call base.wndProc for any messages that they don't handle.
/// </summary>
protected virtual void WndProc(ref Message m)
{
// 此處省略程式碼未知行
// If you add any new messages below (or change the message handling code for any messages)
// please make sure that you also modify AxHost.WndProc to do the right thing and intercept
// messages which the Ocx would own before passing them onto Control.WndProc.
switch ((User32.WM)m.Msg)
{
// 此處省略程式碼未知行
default:
// If we received a thread execute message, then execute it.
if (m.Msg == (int)s_threadCallbackMessage && m.Msg != 0)
{
InvokeMarshaledCallbacks();
return;
}
break;
// 此處省略程式碼未知行
}
// 此處省略程式碼未知行
}
/// <summary>
/// Called on the control's owning thread to perform the actual callback.
/// This empties this control's callback queue, propagating any exceptions
/// back as needed.
/// </summary>
private void InvokeMarshaledCallbacks()
{
ThreadMethodEntry current = null;
lock (_threadCallbackList)
{
if (_threadCallbackList.Count > 0)
{
current = (ThreadMethodEntry)_threadCallbackList.Dequeue();
}
}
// Now invoke on all the queued items.
while (current != null)
{
if (current._method != null)
{
try
{
// If we are running under the debugger, don't wrap asynchronous
// calls in a try catch. It is much better to throw here than pop up
// a thread exception dialog below.
if (NativeWindow.WndProcShouldBeDebuggable && !current._synchronous)
{
InvokeMarshaledCallback(current);
}
else
{
try
{
InvokeMarshaledCallback(current);
}
catch (Exception t)
{
current._exception = t.GetBaseException();
}
}
}
finally
{
current.Complete();
if (!NativeWindow.WndProcShouldBeDebuggable &&
current._exception != null && !current._synchronous)
{
Application.OnThreadException(current._exception);
}
}
}
lock (_threadCallbackList)
{
if (_threadCallbackList.Count > 0)
{
current = (ThreadMethodEntry)_threadCallbackList.Dequeue();
}
else
{
current = null;
}
}
}
}
3、async deadlock程式碼解析
死鎖程式碼示例、反編譯程式碼檢視
https://sharplab.io/很不錯的一個網站,可線上檢視C#編譯後程式碼、中間語言程式碼。
執行過程:
可以看到9和10都在UI執行緒執行,但是UI執行緒已經被10的執行流程佔用,導致9無法將任務設定為完成狀態,陷入死鎖。
編譯後的DeadTask函式
由於編譯的程式碼不清晰,我進行重新命名和程式碼精簡。
可以看到DeadTask返回DeadTaskAsyncStateMachine.Task,看來要整明白AsyncTaskMethodBuilder執行過程,才能清楚來龍去脈了。
private Task<string> DeadTask()
{
DeadTaskAsyncStateMachine stateMachine = new DeadTaskAsyncStateMachine();
stateMachine.tBuilder = AsyncTaskMethodBuilder<string>.Create();
stateMachine.form1 = this;
stateMachine.state1 = -1;
stateMachine.tBuilder.Start(ref stateMachine);
return stateMachine.tBuilder.Task;
}
編譯生成的DeadTaskAsyncStateMachine類
由於編譯的程式碼不清晰,我進行重新命名。
private sealed class DeadTaskAsyncStateMachine : IAsyncStateMachine
{
public int state1;
public AsyncTaskMethodBuilder<string> tBuilder;
public Form1 form1;
private string taskResult;
private TaskAwaiter delay500Awaiter;
private TaskAwaiter<string> helloWorldAwaiter;
private void MoveNext()
{
int num = state1;
string finalResult;
try
{
TaskAwaiter<string> awaiter;
TaskAwaiter awaiter2;
if (num != 0)
{
if (num == 1)
{
awaiter = helloWorldAwaiter;
helloWorldAwaiter = default(TaskAwaiter<string>);
num = (state1 = -1);
goto finalTag;
}
awaiter2 = Task.Delay(500).GetAwaiter();
if (!awaiter2.IsCompleted)
{
num = (state1 = 0);
delay500Awaiter = awaiter2;
DeadTaskAsyncStateMachine stateMachine = this;
tBuilder.AwaitUnsafeOnCompleted(ref awaiter2, ref stateMachine);
return;
}
}
else
{
awaiter2 = delay500Awaiter;
delay500Awaiter = default(TaskAwaiter);
num = (state1 = -1);
}
awaiter2.GetResult();
awaiter = Task.FromResult("Hello world").GetAwaiter();
// 因為awaiter.IsCompleted == true,部分程式碼進行移除
goto finalTag;
finalTag:
finalResult = awaiter.GetResult();
}
catch (Exception exception)
{
state1 = -2;
tBuilder.SetException(exception);
return;
}
state1 = -2;
tBuilder.SetResult(finalResult); // 設定結果,同時設定任務為完成狀態
}
void IAsyncStateMachine.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
this.MoveNext(); // 執行狀態機當前任務,初始狀態state1 = -1
}
private void SetStateMachine(IAsyncStateMachine stateMachine)
{
}
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
{
//ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
this.SetStateMachine(stateMachine);
}
}
關鍵程式碼:
MoveNext原始碼
AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted原始碼:
可以看到將會呼叫函式TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, continueOnCapturedContext: true)。
public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine =>
AsyncMethodBuilderCore.Start(ref stateMachine);
//原始碼地址:
//https://github.com/dotnet/runtime/blob/release/5.0/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskMethodBuilderT.cs#L101
internal static void AwaitUnsafeOnCompleted<TAwaiter>(
ref TAwaiter awaiter, IAsyncStateMachineBox box)
where TAwaiter : ICriticalNotifyCompletion
{
// 執行位置,預設continueOnCapturedContext = true即為繼續在上下文執行
// 最終SynchronizationContext.Current.Post觸發執行stateMachine.MoveNext
if ((null != (object?)default(TAwaiter)) && (awaiter is ITaskAwaiter))
{
ref TaskAwaiter ta = ref Unsafe.As<TAwaiter, TaskAwaiter>(ref awaiter); // relies on TaskAwaiter/TaskAwaiter<T> having the same layout
TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, continueOnCapturedContext: true);
}
// ConfigureAwait(false).GetAwaiter()返回型別為IConfiguredTaskAwaiter,可以避免死鎖
else if ((null != (object?)default(TAwaiter)) && (awaiter is IConfiguredTaskAwaiter))
{
ref ConfiguredTaskAwaitable.ConfiguredTaskAwaiter ta = ref Unsafe.As<TAwaiter, ConfiguredTaskAwaitable.ConfiguredTaskAwaiter>(ref awaiter);
TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, ta.m_continueOnCapturedContext);
}
// 省略程式碼未知行
}
AsyncMethodBuilderCore.Start原始碼:
//原始碼地址
//https://github.com/dotnet/runtime/blob/release/5.0/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilderCore.cs#L21
public static void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
if (stateMachine == null) // TStateMachines are generally non-nullable value types, so this check will be elided
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
}
// enregistrer variables with 0 post-fix so they can be used in registers without EH forcing them to stack
// Capture references to Thread Contexts
Thread currentThread0 = Thread.CurrentThread;
Thread currentThread = currentThread0;
ExecutionContext? previousExecutionCtx0 = currentThread0._executionContext;
ExecutionContext? previousExecutionCtx = previousExecutionCtx0;
SynchronizationContext? previousSyncCtx = currentThread0._synchronizationContext;
try
{
// 執行DeadTaskAsyncStateMachine.MoveNext()
stateMachine.MoveNext();
}
finally
{
// Re-enregistrer variables post EH with 1 post-fix so they can be used in registers rather than from stack
SynchronizationContext? previousSyncCtx1 = previousSyncCtx;
Thread currentThread1 = currentThread;
// The common case is that these have not changed, so avoid the cost of a write barrier if not needed.
if (previousSyncCtx1 != currentThread1._synchronizationContext)
{
// Restore changed SynchronizationContext back to previous
currentThread1._synchronizationContext = previousSyncCtx1;
}
ExecutionContext? previousExecutionCtx1 = previousExecutionCtx;
ExecutionContext? currentExecutionCtx1 = currentThread1._executionContext;
if (previousExecutionCtx1 != currentExecutionCtx1)
{
ExecutionContext.RestoreChangedContextToThread(currentThread1, previousExecutionCtx1, currentExecutionCtx1);
}
}
}
TaskAwaiter.UnsafeOnCompletedInternal原始碼:
// 原始碼地址
//https://github.com/dotnet/runtime/blob/release/5.0/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/TaskAwaiter.cs#L220
internal static void UnsafeOnCompletedInternal(Task task, IAsyncStateMachineBox stateMachineBox, bool continueOnCapturedContext)
{
task.UnsafeSetContinuationForAwait(stateMachineBox, continueOnCapturedContext);
}
Task.UnsafeSetContinuationForAwait原始碼:
// 原始碼地址
//https://github.com/dotnet/runtime/blob/release/5.0/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs#L2513
internal void UnsafeSetContinuationForAwait(IAsyncStateMachineBox stateMachineBox, bool continueOnCapturedContext)
{
// continueOnCapturedContext == true,走這個分支
if (continueOnCapturedContext)
{
SynchronizationContext? syncCtx = SynchronizationContext.Current;
if (syncCtx != null && syncCtx.GetType() != typeof(SynchronizationContext))
{
var tc = new SynchronizationContextAwaitTaskContinuation(syncCtx, stateMachineBox.MoveNextAction, flowExecutionContext: false);
// 新增到m_continuationObject,如果新增失敗則代表任務已經完成,tc直接執行
if (!AddTaskContinuation(tc, addBeforeOthers: false))
{
tc.Run(this, canInlineContinuationTask: false);
}
return;
}
else
{
TaskScheduler? scheduler = TaskScheduler.InternalCurrent;
if (scheduler != null && scheduler != TaskScheduler.Default)
{
var tc = new TaskSchedulerAwaitTaskContinuation(scheduler, stateMachineBox.MoveNextAction, flowExecutionContext: false);
if (!AddTaskContinuation(tc, addBeforeOthers: false))
{
tc.Run(this, canInlineContinuationTask: false);
}
return;
}
}
}
// Otherwise, add the state machine box directly as the continuation.
// If we're unable to because the task has already completed, queue it.
if (!AddTaskContinuation(stateMachineBox, addBeforeOthers: false))
{
ThreadPool.UnsafeQueueUserWorkItemInternal(stateMachineBox, preferLocal: true);
}
}
SynchronizationContextAwaitTaskContinuation原始碼:
// 原始碼地址
//https://github.com/dotnet/runtime/blob/release/5.0/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskContinuation.cs#L364
/// <summary>Task continuation for awaiting with a current synchronization context.</summary>
internal sealed class SynchronizationContextAwaitTaskContinuation : AwaitTaskContinuation
{
/// <summary>SendOrPostCallback delegate to invoke the action.</summary>
private static readonly SendOrPostCallback s_postCallback = static state =>
{
Debug.Assert(state is Action);
((Action)state)();
};
/// <summary>Cached delegate for PostAction</summary>
private static ContextCallback? s_postActionCallback;
/// <summary>The context with which to run the action.</summary>
private readonly SynchronizationContext m_syncContext;
internal SynchronizationContextAwaitTaskContinuation(
SynchronizationContext context, Action action, bool flowExecutionContext) :
base(action, flowExecutionContext)
{
Debug.Assert(context != null);
m_syncContext = context;
}
internal sealed override void Run(Task task, bool canInlineContinuationTask)
{
// If we're allowed to inline, run the action on this thread.
if (canInlineContinuationTask &&
m_syncContext == SynchronizationContext.Current)
{
RunCallback(GetInvokeActionCallback(), m_action, ref Task.t_currentTask);
}
// Otherwise, Post the action back to the SynchronizationContext.
else
{
TplEventSource log = TplEventSource.Log;
if (log.IsEnabled())
{
m_continuationId = Task.NewId();
log.AwaitTaskContinuationScheduled((task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id, task.Id, m_continuationId);
}
// 執行PostAction
RunCallback(GetPostActionCallback(), this, ref Task.t_currentTask);
}
// Any exceptions will be handled by RunCallback.
}
private static void PostAction(object? state)
{
Debug.Assert(state is SynchronizationContextAwaitTaskContinuation);
var c = (SynchronizationContextAwaitTaskContinuation)state;
TplEventSource log = TplEventSource.Log;
if (log.IsEnabled() && log.TasksSetActivityIds && c.m_continuationId != 0)
{
// 呼叫Control.BeginInvoke
c.m_syncContext.Post(s_postCallback, GetActionLogDelegate(c.m_continuationId, c.m_action));
}
else
{
c.m_syncContext.Post(s_postCallback, c.m_action); // s_postCallback is manually cached, as the compiler won't in a SecurityCritical method
}
}
private static Action GetActionLogDelegate(int continuationId, Action action)
{
return () =>
{
Guid activityId = TplEventSource.CreateGuidForTaskID(continuationId);
System.Diagnostics.Tracing.EventSource.SetCurrentThreadActivityId(activityId, out Guid savedActivityId);
try { action(); }
finally { System.Diagnostics.Tracing.EventSource.SetCurrentThreadActivityId(savedActivityId); }
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ContextCallback GetPostActionCallback() => s_postActionCallback ??= PostAction;
}
Task.Delay實現過程
Task.Delay有多種實現,我精簡後畫了大致實現流程,感興趣的同學可以閱讀一下原始碼,部分在coreclr實現。
QueueUseAPC: https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-queueuserapc
SleepEx: https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-sleepex
為什麼IO型、延時任務要採用async
原因: 執行緒池預設的最小工作執行緒數量為CPU核心數,如果不採用async會導致執行緒同步阻塞,需要執行緒池建立更多的工作執行緒來應對的併發。當執行緒池工作執行緒的數量大於最小工作執行緒數量時,工作執行緒的建立速度受限於最小工作執行緒數量,每秒不超過2個,這時候程式會出現假死的情況。執行緒池預設設定最小工作執行緒數量為CPU核心數,主要是希望使用async通過多路複用來提升程式的併發效能。如果舊程式不好改造,快速解決的方法就是通過ThreadPool.SetMinThreads設定最小工作執行緒數量,放開工作執行緒建立速度限制,以多執行緒模型應對更多的併發,雖然系統效能差一些,至少不會假死。
小實驗:
Demo原始碼地址: https://gitee.com/RiverBied/async-demo
啟動Web.Api站點,執行WinForms.App進行測試,不過不要在除錯狀態執行。
HttpClient.GetStringAsync執行過程
可以看到在Windows平臺是通過IOCP觸發回撥事件。在Unix平臺是在SocketAsyncEngine類建立while(true)迴圈的執行執行緒,再通過Wait epoll或kqueue獲取IO事件,最後觸發回撥事件。IOPC為非同步非阻塞IO、epoll為同步非阻塞IO,IOCP、epoll會涉及IO模型、IO多路複用等知識,網上介紹較多,可以自行查閱。同時需要注意AwaitableSocketAsyncEventArgs既繼承SocketAsyncEventArgs類也實現IValueTaskSource介面。
HttpClient.GetStringAsync請求:
NetworkStream.WriteAsync在Windows平臺實現:
NetworkStream.WriteAsync在Unix平臺實現:
async await推薦實踐方法
-
async/await適用於IO型(檔案讀取、網路通訊)、延時型任務。對於計算型任務可以使用Task.Factory建立LongRunning任務,該任務會獨立新建一個後臺執行緒進行處理。
-
關於MySql驅動元件: 建議採用MySqlConnector元件。因為MySqlConnector元件支援非同步IO,MySql.Data元件不支援真實的非同步IO。
-
如果條件允許、儘量使用ConfigureAwait(false)。如果不設定在Winform場景下會呼叫SynchronizationContext.Post通過UI執行緒執行回撥函式,同步方法呼叫非同步方式時會出現死鎖。
-
Task方法替代清單:
同步方法 | 非同步方式 | 描述資訊 |
---|---|---|
task.Wait | await task | 等待一個任務執行完成 |
task.Result | await task | 獲取任務返回結果 |
Task.WaitAny | await Task.WhenAny | 等待其中一個任務執行完成,繼續執行 |
Task.WaitAll | await Task.WhenAll | 等待所有任務執行完成,繼續執行 |
Thread.Sleep | await Task.Delay | 延時幾秒繼續執行 |
附
Demo程式碼地址 : https://gitee.com/RiverBied/async-demo