异步函数async await在wpf都做了什么?

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

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

输出结果

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($"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"; }

输出结果:

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

Copy
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; }

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

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<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类的比较关键的几个函数实现:

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($"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($"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
调用线程无法访问此对象,因为另一个线程拥有该对象

补充
推荐林大佬的一篇文章,也讲的也简洁透彻C# dotnet 自己实现一个线程同步上下文

posted @   RyzenAdorer  阅读(3151)  评论(2编辑  收藏  举报
编辑推荐:
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
点击右上角即可分享
微信分享提示
CONTENTS