諾禾致源、異步函數async await在wpf都做了什麼?

class Program
{
static async Task Main(string[] args)
{
System.Console.WriteLine("ThreadIdisThread:Thread.CurrentThread.ManagedThreadId,IsThreadPool:Thread.CurrentThread.IsThreadPoolThread");varresult=awaitExampleTask(2);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 ExampleTask(int Second)
{
await Task.Delay(TimeSpan.FromSeconds(Second));
return $“It’s Async Completed in {Second} seconds”;
}
}
輸出結果

Copy
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運轉,猜猜會輸出啥?

Copy
private async void Async_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine("ThreadIdisThread:Thread.CurrentThread.ManagedThreadId,IsThreadPool:Thread.CurrentThread.IsThreadPoolThread");varresult=awaitExampleTask(2);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 ExampleTask(int Second)
{
await Task.Delay(TimeSpan.FromSeconds(Second));
return $“It’s Async Completed in {Second} seconds”;
}
輸出結果:

Copy
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,看看反編譯的兩段代碼能否有不同之處:

控制檯應用:

Copy
internal class Program
{
[DebuggerStepThrough]
private static Task Main(string[] args)
{
Program.
d__0
d__ = new Program.
d__0();

d__.args = args;

d__.<>t__builder = AsyncTaskMethodBuilder.Create();

d__.<>1__state = -1;

d__.<>t__builder.Start<Program.
d__0>(ref
d__);
return
d__.<>t__builder.Task;
}

[DebuggerStepThrough]
private static Task<string> ExampleTask(int Second)
{
	Program.d__1 d__ = new Program.d__1();
	d__.Second = Second;
	d__.<>t__builder = AsyncTaskMethodBuilder<string>.Create();
	d__.<>1__state = -1;
	d__.<>t__builder.Start<Program.d__1>(ref d__);
	return d__.<>t__builder.Task;
}
[DebuggerStepThrough]
private static void 

(string[] args)
{
Program.Main(args).GetAwaiter().GetResult();
}
}
WPF:

Copy
public class MainWindow : Window, IComponentConnector
{
public MainWindow()
{
this.InitializeComponent();
}
[DebuggerStepThrough]
private void Async_Click(object sender, RoutedEventArgs e)
{
MainWindow.d__1 d__ = new MainWindow.d__1();
d__.<>4__this = this;
d__.sender = sender;
d__.e = e;
d__.<>t__builder = AsyncVoidMethodBuilder.Create();
d__.<>1__state = -1;
d__.<>t__builder.Start<MainWindow.d__1>(ref d__);
}
[DebuggerStepThrough]
private Task ExampleTask(int Second)
{
MainWindow.d__3 d__ = new MainWindow.d__3();
d__.<>4__this = this;
d__.Second = Second;
d__.<>t__builder = AsyncTaskMethodBuilder.Create();
d__.<>1__state = -1;
d__.<>t__builder.Start<MainWindow.d__3>(ref d__);
return 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;
}
我們能夠看到完整是分歧的,沒有任何區別,爲什麼編譯器生成的代碼是分歧的,卻會產生不一樣的結果,我們看看創立和啓動狀態機代碼局部的完成:

Copy
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(ref stateMachine);
}
[DebuggerStepThrough]
public static void Start(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類的比擬關鍵的幾個函數完成:

Copy
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那段代碼大約能夠改寫成如此:

Copy
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");           

}
輸出結果:

Copy
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辦法,我們來考證下:

我們將代碼改爲以下:

Copy
private async void Async_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine("ThreadIdisThread:Thread.CurrentThread.ManagedThreadId,IsThreadPool:Thread.CurrentThread.IsThreadPoolThread");varresult=awaitExampleTask(2).ConfigureAwait(false);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”);
}
輸出:

Copy
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:

Copy
private async void Async_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine("ThreadIdisThread:Thread.CurrentThread.ManagedThreadId,IsThreadPool:Thread.CurrentThread.IsThreadPoolThread");varresult=awaitExampleTask(2).ConfigureAwait(false);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”);
}
拋出異常:

Copy
調用線程無法訪問此對象,由於另一個線程具有該對象

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章