
本文共 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 TaskExampleTask(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 TaskExampleTask(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");}
抛出异常:
调用线程无法访问此对象,因为另一个线程拥有该对象
补充
推荐林大佬的一篇文章,也讲的也简洁透彻发表评论
最新留言
关于作者
