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

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

抛出异常:

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

异步函数async await在wpf都做了什么?的更多相关文章

  1. .net 异步函数 Async await

    .net  异步函数  Async await 一旦为函数添加async关键字 该函数就是一个异步函数. 异步方法必须返回 void 或 Task<> 类型. public static ...

  2. 简单的异步函数async/await例子

    function resolveAfter2Seconds(x){ return new Promise(resolve => { setTimeout(() => { resolve(x ...

  3. 温故知新,CSharp遇见异步编程(Async/Await),聊聊异步编程最佳做法

    什么是异步编程(Async/Await) Async/Await本质上是通过编译器实现的语法糖,它让我们能够轻松的写出简洁.易懂.易维护的异步代码. Async/Await是C# 5引入的关键字,用以 ...

  4. 抓住异步编程async/await语法糖的牛鼻子: SynchronizationContext

    长话短说,本文带大家抓住异步编程async/await语法糖的牛鼻子: SynchronizationContext 引言 C#异步编程语法糖async/await,使开发者很容易就能编写异步代码. ...

  5. MVC+Spring.NET+NHibernate .NET SSH框架整合 C# 委托异步 和 async /await 两种实现的异步 如何消除点击按钮时周围出现的白线? Linq中 AsQueryable(), AsEnumerable()和ToList()的区别和用法

    MVC+Spring.NET+NHibernate .NET SSH框架整合   在JAVA中,SSH框架可谓是无人不晓,就和.NET中的MVC框架一样普及.作为一个初学者,可以感受到.NET出了MV ...

  6. javascript异步编程 Async/await

    Async/await Async/await 在学习他之前应当补充一定的 promise 知识 它是一种与 promise 相配合的特殊语法,目前被认为是异步编程的终级解决方案 值得我们每一个人学习 ...

  7. 异步编程Async/await关键字

    异步编程Async \await 关键字在各编程语言中的发展(出现)纪实. 时间 语言版本 2012.08.15 C#5.0(VS2012) 2015.09.13 Python 3.5 2016.03 ...

  8. 令人清爽的异步函数async、await

    1.什么是async.await? async用于声明一个函数是异步的.而await从字面意思上是"等待"的意思,就是用于等待异步完成.并且await只能在async函数中使用; ...

  9. .net 异步编程async & await关键字的思考

    C# 5.0引入了两个关键字 async和await,这两个关键字在很大程度上帮助我们简化了异步编程的实现代码,而且TPL中的task与async和await有很大的关系 思考了一下异步编程中的asy ...

随机推荐

  1. shell脚本常用命令汇总

    一.shell脚本概述和入门 (1)shell脚本是一个命令行解释器,它接收应用程序/用户命令,然后调用操作系统内核 (2)shell脚本的常用执行方式: 第一种:采用bash或sh+脚本的相对路径或 ...

  2. java基础一(标识符、数据类型及注释)

    1.标识符 标识符可以由字母.数字(不能以数字开头).下划线(_).美元符($)组成: 标识符不能包含 @.%.空格等其它特殊字符: 标识符区分大小写: 2.数据类型 int->整型->4 ...

  3. 聊聊Grpc使用中的坑以及怎么填

    总所周知,随着云技术的发展,和业务的复杂度的上升,越来越多的系统开始拆分成独立的子模块微服务.模块之间免不了相互通信.但是随着业务量的增多,传输量也随之增大,偶发性timeout,无响应, 传输量过大 ...

  4. 【雕爷学编程】Arduino动手做(46)---电阻应变片传感器

    37款传感器与模块的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止37种的.鉴于本人手头积累了一些传感器和模块,依照实践出真知(一定要动手做)的理念,以学习和交流为目的,这里 ...

  5. 微信小程序通信录

    第一步:phone.wxml中 <view bindlongtap="clickPhone">{{phoneNum}}</view> 第二步:phone.j ...

  6. jQuery学习的几个不是问题的问题

    原文在我的GitHubhttps://www.sogeisetsugo.tk/myBlog/jQuery学习的几个不是问题的问题

  7. MyBatis In的使用

    http://blog.csdn.net/unei66/article/details/17792503 MyBatis In的使用 标签: mybatisin 2014-01-03 16:23 74 ...

  8. Gym101630A Archery Tournament

    题目链接:https://vjudge.net/problem/Gym-101630A 题目大意: 有\(n\)个操作,每次输入\(t\) \(x\) \(y\)\((t=1,2; -10^9 \le ...

  9. Golang源码学习:调度逻辑(二)main goroutine的创建

    接上一篇继续分析一下runtime.newproc方法. 函数签名 newproc函数的签名为 newproc(siz int32, fn *funcval) siz是传入的参数大小(不是个数):fn ...

  10. step over、step into、step into my code、step out、run to cursor

    红 step over 跳过子函数 黄 step into 进入子函数 蓝 step into my code 不执行源码的子函数执行自己的 黑 step out 跳出当前函数 绿 run to cu ...