zoukankan      html  css  js  c++  java
  • 异步函数async await在wpf都做了什么?

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

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

    输出结果

    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
    

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

    一.SynchronizationContext(同步上下文)

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

    控制台应用:

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

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

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

    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有个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 自己实现一个线程同步上下文

  • 相关阅读:
    Linux 资源监控整体分析-TOP
    GO基础之闭包
    GO基础之函数的高级用法
    GO基础之函数
    GO基础之流程控制语句
    数据结构导论(第二章线性表)
    JVM 参数配置
    GO基础之变量的使用
    数据结构导论(第一章概论)
    网络经济与企业管理(第11章:企业文化管理)
  • 原文地址:https://www.cnblogs.com/ryzen/p/13062963.html
Copyright © 2011-2022 走看看