异步函数async await在wpf都做了什么?
发布日期:2021-05-09 06:18:02 浏览次数:18 分类:博客文章

本文共 9850 字,大约阅读时间需要 32 分钟。

首先我们来看一段控制台应用代码:

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
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:FalseThread Id is Thread:4,Is Thread Pool:TrueIt's Async Completed in 2 secondsAsync 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
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:FalseThread Id is Thread:1,Is Thread Pool:FalseIt's Async Completed in 2 secondsAsync Completed

这时候你肯定是想说,小朋友,你是否有很多问号????,我们接下看下去

一.SynchronizationContext(同步上下文)

首先我们知道async await 异步函数本质是状态机,我们通过反编译工具dnspy,看看反编译的两段代码是否有不同之处:

控制台应用:

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

我们可以看到完全是一致的,没有任何区别,为什么编译器生成的代码是一致的,却会产生不一样的结果,我们看看创建和启动状态机代码部分的实现:

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找到关于

的介绍,有兴趣的朋友可以去阅读以下,以下是各个.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:FalseThread Id is Thread:1,Is Thread Pool:FalseIt's Async Completed in 2 secondsAsync 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:FalseThread Id is Thread:4,Is Thread Pool:TrueIt's Async Completed in 2 secondsAsync 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");}

抛出异常:

调用线程无法访问此对象,因为另一个线程拥有该对象

补充

推荐林大佬的一篇文章,也讲的也简洁透彻

上一篇:理解Task和async await
下一篇:.NET Core 3 WPF MVVM框架 Prism系列之对话框服务

发表评论

最新留言

能坚持,总会有不一样的收获!
[***.219.124.196]2025年05月10日 08时07分17秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章