首先我們來看一段控制檯應用程式碼:
class Program
{
static async Task Main(string[] args)
{
System.Console.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
var result = await ExampleTask(2);
System.Console.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
System.Console.WriteLine(result);
Console.WriteLine("Async Completed");
}
private static async Task<string> ExampleTask(int Second)
{
await Task.Delay(TimeSpan.FromSeconds(Second));
return $"It's Async Completed in {Second} seconds";
}
}
輸出結果
Thread Id is Thread:1,Is Thread Pool:False
Thread Id is Thread:4,Is Thread Pool:True
It's Async Completed in 2 seconds
Async Completed
如果這段程式碼在WPF執行,猜猜會輸出啥?
private async void Async_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
var result= await ExampleTask(2);
Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
Debug.WriteLine(result);
Debug.WriteLine("Async Completed");
}
private async Task<string> ExampleTask(int Second)
{
await Task.Delay(TimeSpan.FromSeconds(Second));
return $"It's Async Completed in {Second} seconds";
}
輸出結果:
Thread Id is Thread:1,Is Thread Pool:False
Thread Id is Thread:1,Is Thread Pool:False
It's Async Completed in 2 seconds
Async Completed
這時候你肯定是想說,小朋友,你是否有很多問號????,我們接下看下去
一.SynchronizationContext(同步上下文)
首先我們知道async await 非同步函式本質是狀態機,我們通過反編譯工具dnspy,看看反編譯的兩段程式碼是否有不同之處:
控制檯應用:
internal class Program
{
[DebuggerStepThrough]
private static Task Main(string[] args)
{
Program.<Main>d__0 <Main>d__ = new Program.<Main>d__0();
<Main>d__.args = args;
<Main>d__.<>t__builder = AsyncTaskMethodBuilder.Create();
<Main>d__.<>1__state = -1;
<Main>d__.<>t__builder.Start<Program.<Main>d__0>(ref <Main>d__);
return <Main>d__.<>t__builder.Task;
}
[DebuggerStepThrough]
private static Task<string> ExampleTask(int Second)
{
Program.<ExampleTask>d__1 <ExampleTask>d__ = new Program.<ExampleTask>d__1();
<ExampleTask>d__.Second = Second;
<ExampleTask>d__.<>t__builder = AsyncTaskMethodBuilder<string>.Create();
<ExampleTask>d__.<>1__state = -1;
<ExampleTask>d__.<>t__builder.Start<Program.<ExampleTask>d__1>(ref <ExampleTask>d__);
return <ExampleTask>d__.<>t__builder.Task;
}
[DebuggerStepThrough]
private static void <Main>(string[] args)
{
Program.Main(args).GetAwaiter().GetResult();
}
}
WPF:
public class MainWindow : Window, IComponentConnector
{
public MainWindow()
{
this.InitializeComponent();
}
[DebuggerStepThrough]
private void Async_Click(object sender, RoutedEventArgs e)
{
MainWindow.<Async_Click>d__1 <Async_Click>d__ = new MainWindow.<Async_Click>d__1();
<Async_Click>d__.<>4__this = this;
<Async_Click>d__.sender = sender;
<Async_Click>d__.e = e;
<Async_Click>d__.<>t__builder = AsyncVoidMethodBuilder.Create();
<Async_Click>d__.<>1__state = -1;
<Async_Click>d__.<>t__builder.Start<MainWindow.<Async_Click>d__1>(ref <Async_Click>d__);
}
[DebuggerStepThrough]
private Task<string> ExampleTask(int Second)
{
MainWindow.<ExampleTask>d__3 <ExampleTask>d__ = new MainWindow.<ExampleTask>d__3();
<ExampleTask>d__.<>4__this = this;
<ExampleTask>d__.Second = Second;
<ExampleTask>d__.<>t__builder = AsyncTaskMethodBuilder<string>.Create();
<ExampleTask>d__.<>1__state = -1;
<ExampleTask>d__.<>t__builder.Start<MainWindow.<ExampleTask>d__3>(ref <ExampleTask>d__);
return <ExampleTask>d__.<>t__builder.Task;
}
[DebuggerNonUserCode]
[GeneratedCode("PresentationBuildTasks", "4.8.1.0")]
public void InitializeComponent()
{
bool contentLoaded = this._contentLoaded;
if (!contentLoaded)
{
this._contentLoaded = true;
Uri resourceLocater = new Uri("/WpfApp1;component/mainwindow.xaml", UriKind.Relative);
Application.LoadComponent(this, resourceLocater);
}
}
private bool _contentLoaded;
}
我們可以看到完全是一致的,沒有任何區別,為什麼編譯器生成的程式碼是一致的,卻會產生不一樣的結果,我們看看建立和啟動狀態機程式碼部分的實現:
public static AsyncVoidMethodBuilder Create()
{
SynchronizationContext synchronizationContext = SynchronizationContext.Current;
if (synchronizationContext != null)
{
synchronizationContext.OperationStarted();
}
return new AsyncVoidMethodBuilder
{
_synchronizationContext = synchronizationContext
};
}
[DebuggerStepThrough]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Start<[Nullable(0)] TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
AsyncMethodBuilderCore.Start<TStateMachine>(ref stateMachine);
}
[DebuggerStepThrough]
public static void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
if (stateMachine == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
}
Thread currentThread = Thread.CurrentThread;
Thread thread = currentThread;
ExecutionContext executionContext = currentThread._executionContext;
ExecutionContext executionContext2 = executionContext;
SynchronizationContext synchronizationContext = currentThread._synchronizationContext;
try
{
stateMachine.MoveNext();//狀態機執行程式碼
}
finally
{
SynchronizationContext synchronizationContext2 = synchronizationContext;
Thread thread2 = thread;
if (synchronizationContext2 != thread2._synchronizationContext)
{
thread2._synchronizationContext = synchronizationContext2;
}
ExecutionContext executionContext3 = executionContext2;
ExecutionContext executionContext4 = thread2._executionContext;
if (executionContext3 != executionContext4)
{
ExecutionContext.RestoreChangedContextToThread(thread2, executionContext3, executionContext4);
}
}
}
在這裡總結下:
- 建立狀態機的Create函式通過SynchronizationContext.Current獲取到當前同步執行上下文
- 啟動狀態機的Start函式之後通過MoveNext函式執行我們的非同步方法
- 這裡還有一個小提示,不管async函式裡面有沒有await,都會生成狀態機,只是MoveNext函式執行同步方法,因此沒await的情況下避免將函式標記為async,會損耗效能
同樣的這裡貌似沒能獲取到原因,但是有個很關鍵的地方,就是Create函式為啥要獲取當前同步執行上下文,之後我從MSDN找到關於SynchronizationContext
的介紹,有興趣的朋友可以去閱讀以下,以下是各個.NET框架使用的SynchronizationContext:
SynchronizationContext | 預設 |
---|---|
WindowsFormsSynchronizationContext | WindowsForm |
DispatcherSynchronizationContext | WPF/Silverlight |
AspNetSynchronizationContext | ASP.NET |
我們貌似已經一步步接近真相了,接下來我們來看看DispatcherSynchronizationContext
二.DispatcherSynchronizationContext
首先來看看DispatcherSynchronizationContext類的比較關鍵的幾個函式實現:
public DispatcherSynchronizationContext(Dispatcher dispatcher, DispatcherPriority priority)
{
if (dispatcher == null)
{
throw new ArgumentNullException("dispatcher");
}
Dispatcher.ValidatePriority(priority, "priority");
_dispatcher = dispatcher;
_priority = priority;
SetWaitNotificationRequired();
}
//同步執行
public override void Send(SendOrPostCallback d, object state)
{
if (BaseCompatibilityPreferences.GetInlineDispatcherSynchronizationContextSend() && _dispatcher.CheckAccess())
{
_dispatcher.Invoke(DispatcherPriority.Send, d, state);
}
else
{
_dispatcher.Invoke(_priority, d, state);
}
}
//非同步執行
public override void Post(SendOrPostCallback d, object state)
{
_dispatcher.BeginInvoke(_priority, d, state);
}
我們貌似看到了熟悉的東西了,Send函式呼叫Dispatcher的Invoke函式,Post函式呼叫Dispatcher的BeginInvoke函式,那麼是否WPF執行非同步函式之後會呼叫這裡的函式嗎?我用dnspy進行了除錯:
我通過除錯之後發現,當等待執行完整個狀態機的之後,也就是兩秒後跳轉到該Post函式,那麼,我們可以將之前的WPF那段程式碼大概可以改寫成如此:
private async void Async_Click(object sender, RoutedEventArgs e)
{
//async生成狀態機的Create函式。獲取到UI主執行緒的同步執行上下文
DispatcherSynchronizationContext synchronizationContext = (DispatcherSynchronizationContext)SynchronizationContext.Current;
//UI主執行緒執行
Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
//開始在狀態機的MoveNext執行該非同步操作
var result= await ExampleTask(2);
//等待兩秒,非同步執行完成,再在同步上下文非同步執行
synchronizationContext.Post((state) =>
{
//模仿_dispatcher.BeginInvoke
Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
Debug.WriteLine(result);
Debug.WriteLine("Async Completed");
},"Post");
}
輸出結果:
Thread Id is Thread:1,Is Thread Pool:False
Thread Id is Thread:1,Is Thread Pool:False
It's Async Completed in 2 seconds
Async Completed
也就是asyn負責生成狀態機和執行狀態機,await將程式碼分為兩部分,一部分是非同步執行狀態機部分,一部分是非同步執行完之後,通過之前拿到的DispatcherSynchronizationContext,再去非同步執行接下來的部分。我們可以通過dnspy除錯DispatcherSynchronizationContext的 _dispatcher欄位的Thread屬性,知道Thread為UI主執行緒,而同步介面UI控制元件的時候,也就是通過Dispatcher的BeginInvoke函式去執行同步的
三.Task.ConfigureAwait
Task有個ConfigureAwait方法,是可以設定是否對Task的awaiter的延續任務執行原始上下文,也就是為true時,是以一開始那個UI主執行緒的DispatcherSynchronizationContext執行Post方法,而為false,則以await那個Task裡面的DispatcherSynchronizationContext執行Post方法,我們來驗證下:
我們將程式碼改為以下:
private async void Async_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
var result= await ExampleTask(2).ConfigureAwait(false);
Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
Debug.WriteLine(result);
Debug.WriteLine($"Async Completed");
}
輸出:
Thread Id is Thread:1,Is Thread Pool:False
Thread Id is Thread:4,Is Thread Pool:True
It's Async Completed in 2 seconds
Async Completed
結果和控制檯輸出的一模一樣,且通過dnspy斷點除錯依舊進入到DispatcherSynchronizationContext的Post方法,因此我們也可以證明我們上面的猜想,而且預設ConfigureAwait的引數是為true的,我們還可以將非同步結果賦值給UI介面的Text block:
private async void Async_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
var result= await ExampleTask(2).ConfigureAwait(false);
Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
this.txt.Text = result;//修改部分
Debug.WriteLine($"Async Completed");
}
丟擲異常:
呼叫執行緒無法訪問此物件,因為另一個執行緒擁有該物件