.Net同僚对于async和await的话题真的是经久不衰,这段时间又看到了关于这方面的讨论,最终也没有得出什么结论,其实要弄懂这个东西,并没有那么复杂,简单的从本质上来讲,就是一句话,async 和await异步的本质就是状态机+线程环境上下文的流转,由状态机向前推进执行,上下文进行环境切换。
在状态机向前推进的时候第一次的movenext会将当前线程的环境上下文保存起来,然后由TaskScheduler调度是否去线程池拿新线程执行这个task,等到后续推进到最后的movenext的时候,里面设置好结果,异常之后,回调则需要运行在调用await之前的环境上下文中去,这里说的是环境上下文,而并非是线程,所以当前环境上下文在await之前是A线程的上下文,在遇到await结束之后可能是B线程的环境上下文,并且异步是异步,线程是线程,异步不一定多线程,这两个不是等价的。
接下来,我会讲一些环境上下文,同步上下文的知识,以及在cs程序中,框架对于同步上下文的封装。
ExecutionContext表示管理当前线程的执行上下文。针对此类,官网的解释是该 ExecutionContext 类为与逻辑执行线程相关的所有信息提供单个容器。 在.NET Framework中,这包括安全上下文、调用上下文和同步上下文。 在 .NET Core 中,不支持安全上下文和调用上下文,但是,模拟上下文和区域性通常通过执行上下文流动。 简单来说,这个类就是存放当前线程所有环境信息的容器,在net framework 和net core中,略有不同,后者不包括同步上下文。
public class TestTask { //堆代码 duidaima.com public static AsyncLocal<int> id; } private async void button1_Click(object sender, EventArgs e) { //var sss = new MyTask(() => { Console.WriteLine(111); }); //await sss; var exce = ExecutionContext.IsFlowSuppressed(); TestTask.id = new AsyncLocal<int>() { Value = 1 }; // var asynclo=ExecutionContext.SuppressFlow(); var con1 = ExecutionContext.Capture(); var a = ExecutionContext.SuppressFlow(); exce = ExecutionContext.IsFlowSuppressed(); await Task.Delay(1000); var con2 = ExecutionContext.Capture(); ExecutionContext.Run(con2, s => { var sss = TestTask.id.Value; }, null); await Task.Delay(1000); ExecutionContext.Restore(con1); var sssa = TestTask.id.Value; }在上面的代码中,我首先定义了一个AsyncLocal 存放int类型的一个变量,在winform中我界面添加一个按钮,在点击事件中写下了如下代码,在第一行代码中调用了ExecutionContext.IsFlowSuppressed方法,这个方法是判断是否停止当前上下文的流转,
在刚开始运行的时候,这个返回结果是False,说明我们没有停止流转,是可以正常流转,在第二行代码中,我们给AsyncLocal变量赋值,设置Value为1;第三行中,我们使用了ExecutionContext.Capture方法,这个方法是捕获当前上下文信息,然后赋值给了con1变量,在往下走,我们调用了SuppressFlow方法,这个方法是我们阻止了当前上下文的流转,也就是说这个上下文是和await之后的上下文是不一样的,然后我们在判断IsFlowSuppressed的时候返回的就是true了,停止了流转,然后我们异步Delay1秒,然后我们捕获异步之后的当前线程的上下文信息,然后在这里我们捕获我们这个线程的上下文信息。
接下来调用了ExecutionContext.Run方法,这个方法是将第二个参数的委托代码,运行在指定的上下文中去,这块这个run方法我们用不用其实都不影响演示效果,在这代码中,我们获取到id.Value就和上面的不一样获取的是默认值0,而不是上面定义的1,这就是因为我们停止了上下文流转,导致await前后不是同一个上下文,所以获取不到这个Value,如果我们不调用SuppressFlow,那在await之后就是上一个的上下文信息,获取到的Value也就是原来的1,在往下走,我们在Delay一下,在调用Restore方法,这个方法是将当前线程的上下文替换为指定的上下文信息,将指定上下文信息还原到当前线程,然后在获取的Value就是1了。
在代码中执行这段代码,在Task.Run里面加入断点,就可以看到,在new TextBox之前,SynchronizationContext.Current获取到的是null,在之后获取到的是WindowsFormsSynchronizationContext的对象,由此可以看出所有的Control控件,哪怕都在子线程中创建,其也依旧属于UI线程。
await AddText();this.Controls.Add(TextBox) ; JextBox.Text = "堆代码 duidaima.com”; public Task AddText() { var con=WindowsFormsSynchronizationContext.Current; return Task.Run(() => { var c = SynchronizationContext.Current; TextBox = new TextBox(); var b = SynchronizationContext.Current; }); }