.net core Task的学习理解(下) 电脑版发表于:2021/8/20 13:31 ![.netcore](https://img.tnblog.net/arcimg/hb/c857299a86d84ee7b26d181a31e58234.jpg ".netcore") >#.net core Task的学习理解(下) [TOC] Task.GetResult与死锁 ------------ tn2>通过下面的代码,可能会造成死锁的情况。 ```csharp static async Task Main(string[] args) { Console.WriteLine("Test:Start"); DoSthAsync().GetAwaiter().GetResult(); Console.WriteLine("Test:END"); Console.ReadLine(); } static async Task DoSthAsync() { Console.WriteLine("DoSthAsync: START"); await Task.Delay(100); Console.WriteLine("DoSthAsync: END"); } ``` tn2>但是通过如下代码就可以完成运行。这将与同步上下文有关,也就是`SynchronizationContext`类 ```csharp static async Task Main(string[] args) { Console.WriteLine("Test:Start"); DoSthAsync().GetAwaiter().GetResult(); Console.WriteLine("Test:END"); Console.ReadLine(); } static async Task DoSthAsync() { Console.WriteLine("DoSthAsync: START"); await Task.Delay(100).ConfigureAwait(continueOnCapturedContext:false); Console.WriteLine("DoSthAsync: END"); } ``` ![](https://img.tnblog.net/arcimg/hb/09ca28d7cca545a2b83db5b69d36dda0.png) SynchronizationContext与Task回调处理 ------------ tn2>Task不仅可以让TaskScheduler调用,还可以使用SynchronizationContext。 TaskAwaiter在实际注册回调的时候会对回调进行包装,决定了回调会在哪里执行。 当SynchronizationContext.Current不为为null时将会到SynchronizationContext上执行回调,为空是才会去判断TaskScheduler。(具体示意图如下) ![](https://img.tnblog.net/arcimg/hb/2998e1f06a274e869df7e8edd962987f.png) >### 自定义SynchronizationContext ```csharp class MaxConcurrencySynchronizationContext: SynchronizationContext { /// <summary> /// 创建一个信号量 /// </summary> private readonly SemaphoreSlim _semaphore; public MaxConcurrencySynchronizationContext(int maxConcurrencyLevel) => _semaphore = new SemaphoreSlim(maxConcurrencyLevel); public override void Post(SendOrPostCallback d, object state) { _semaphore.Wait(); try { Console.WriteLine("MaxConcurrencySynchronizationContext.ThreadId:{0}",Thread.CurrentThread.ManagedThreadId); d(state); } finally { _semaphore.Release(); } } public override void Send(SendOrPostCallback d, object state) => throw new NotImplementedException(); } ``` tn2>添加测试代码 ```csharp static void Main(string[] args) { Task.Run(() => { var mcs = new MaxConcurrencySynchronizationContext(1); // 设置为我们自定义的同步上下文 SynchronizationContext.SetSynchronizationContext(mcs); mcs.Post(_ => { Test(); }, null); }); Console.ReadLine(); } static void Test() { Console.WriteLine("Test:Start"); DoSthAsync().GetAwaiter().GetResult(); Console.WriteLine("Test:END"); } static async Task DoSthAsync() { Console.WriteLine("DoSthAsync: START"); await Task.Delay(100); Console.WriteLine("DoSthAsync: END"); } ``` tn2>由于最大等待数量为1,所以当我们第二次在Task.Delay的时候就会被阻塞起。 ![](https://img.tnblog.net/arcimg/hb/b0c0dcb7b0f2417eaa9c164a63b7200e.png) tn2>而之前我们看到的`.ConfigureAwait(continueOnCapturedContext:false);`,这段代码的意思就是:不管你有没有同步上下文我都会到TaskScheduler上去执行。 ```csharp static void Main(string[] args) { Task.Run(() => { var mcs = new MaxConcurrencySynchronizationContext(1); // 设置为我们自定义的同步上下文 SynchronizationContext.SetSynchronizationContext(mcs); mcs.Post(_ => { Test(); }, null); }); Console.ReadLine(); } static void Test() { Console.WriteLine("Test:Start"); DoSthAsync().GetAwaiter().GetResult(); Console.WriteLine("Test:END"); } static async Task DoSthAsync() { Console.WriteLine("DoSthAsync: START"); await Task.Delay(100).ConfigureAwait(false); Console.WriteLine("DoSthAsync: END"); } ``` ![](https://img.tnblog.net/arcimg/hb/2d5defd244ae46078cdf8b589a8257a8.png) ValueTask ------------ tn2>ValueTask与Task类似,但它是值类型的。`ValueTask<TResult>`提供了一个AsTask方法,可根据需要获取的常规task(例如用作Task.WhenAll或Task.WhenAny调用的某个元素),不过多数情况下只是安装普通task那样调用await操作。<br/> `ValueTask<TResult>`相比于`Task<TResult>`优势何在?其实体现在堆内存分配和垃圾回收上。`Task<TResult>`是一个类,虽然有时异步基础架构会复用已创建的`Task<TResult>`对象。但多数async方法需要创建新的`Task<TResult>`对象。一般情况下,.NET创建对象的性能消耗不足为虑,但如果频繁创建对象或者遇到性能敏感的代码,就需要尽量避免创建新对象。<br/> 如果在async方法中对某个尚未完成的操作使用await,那么创建对象是不可避免的,虽然此时方法会立即返回,但它需要安排一个延续。当操作完成时,该延续负责执行async方法中的其他语句。大部分async方法中的操作不会在await前执行完成。此时使用使用`ValueTask<TResult>`没有任何优势,可能还会导致性能下降。<br/> 有时task在await之前便已完成,这个时候就能体现出`ValueTask<TResult>`的强大了 ```csharp public sealed class ByteStream : IDisposable { private readonly Stream stream; private readonly byte[] buffer; private int position; private int bufferedBytes; public ByteStream(Stream stream) { this.stream = stream; // 8KB 缓冲区的大小,根本不需要await操作 buffer = new byte[1024 * 8]; } public async ValueTask<byte?> ReadByteAsync() { if (position == bufferedBytes) { position = 0; // 读取数据且不需要同步上下文执行 bufferedBytes = await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); // 读到末尾了 if (bufferedBytes == 0) { return null; } } // 返回缓冲区的下一个字节 return buffer[position++]; } public void Dispose() { stream.Dispose(); } } ``` ![](https://img.tnblog.net/arcimg/hb/5376056179a14518af96ae0dc7fbd6fc.png) tn>主要参考于黄凯华老师老师的演讲:https://www.bilibili.com/video/BV1K3411r75N?p=5 其他文献: 《C#深入浅出(第四版)》 Setephen Toub的:https://devblogs.microsoft.com/pfxteam/executioncontext-vs-synchronizationcontext/ Sergey:https://devblogs.microsoft.com/premier-developer/extending-the-async-methods-in-c/ https://stackoverflow.com/questions/50059704/implementing-async-return-types-on-net-standard