首先咱們來看一段控制檯應用代碼:html
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"; } }
輸出結果c#
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
這時候你確定是想說,小朋友,你是否有不少問號????,咱們接下看下去async
首先咱們知道async await 異步函數本質是狀態機,咱們經過反編譯工具dnspy,看看反編譯的兩段代碼是否有不一樣之處:ide
控制檯應用:函數
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; }
咱們能夠看到徹底是一致的,沒有任何區別,爲何編譯器生成的代碼是一致的,卻會產生不同的結果,咱們看看建立和啓動狀態機代碼部分的實現:post
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函數爲啥要獲取當前同步執行上下文,以後我從MSDN找到關於SynchronizationContext
的介紹,有興趣的朋友能夠去閱讀如下,如下是各個.NET框架使用的SynchronizationContext:
SynchronizationContext | 默認 |
---|---|
WindowsFormsSynchronizationContext | WindowsForm |
DispatcherSynchronizationContext | WPF/Silverlight |
AspNetSynchronizationContext | ASP.NET |
咱們貌似已經一步步接近真相了,接下來咱們來看看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的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"); }
拋出異常:
調用線程沒法訪問此對象,由於另外一個線程擁有該對象
補充
推薦林大佬的一篇文章,也講的也簡潔透徹C# dotnet 本身實現一個線程同步上下文