且Result属性和.Wait()方法一样会阻塞线程。此等问题在Web服务程序里面一样存在。(区别:UI单次线程程序和web服务程序都会释放主线程,不同的是Web服务线程不一定会回到原来的主线程,而UI程序一定会回到原来的UI线程)
我们前面说过,.net为什么会这么智能的自动释放主线程然后等待异步执行完毕后又回到主线程是因为SynchronizationContext的功劳。
但这里有个例外,那就是控制台程序里面是没有SynchronizationContext的。所以这段代码放在控制台里面运行是没有问题的。
static void Main(string[] args) { Console.WriteLine(Thread.CurrentThread.ManagedThreadId); GetUlrString().Wait(); Console.WriteLine(Thread.CurrentThread.ManagedThreadId); Console.ReadKey(); } Task<string> GetUlrString(string url) { using (HttpClient http = new HttpClient()) { Console.WriteLine(Thread.CurrentThread.ManagedThreadId); return await http.GetStringAsync(url); } }
打印出来的都是同一个线程ID
使用AsyncHelper在同步代码里面调用异步但可是,可但是,我们必须在同步方法里面执行异步怎办?办法肯定是有的
我们首先定义一个AsyncHelper静态类:
static class AsyncHelper { TaskFactory _myTaskFactory = new TaskFactory(CancellationToken.None, TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default); public static TResult RunSync<TResult>(Func<Task<TResult>> func) { return _myTaskFactory.StartNew(func).Unwrap().GetAwaiter().GetResult(); } RunSync(Func<Task> func) { _myTaskFactory.StartNew(func).Unwrap().GetAwaiter().GetResult(); } }
然后调用异步:
private void button7_Click(object sender, EventArgs e) { label1.Text = AsyncHelper.RunSync(() => GetUlrString()); }
这样就不会死锁了。
ConfigureAwait除了AsyncHelper我们还可以使用Task的ConfigureAwait方法来避免死锁
private void button7_Click(object sender, EventArgs e) { label1.Text = GetUlrString().Result; } public async Task<string> GetUlrString(string url) { using (HttpClient http = new HttpClient()) { return await http.GetStringAsync(url).ConfigureAwait(false); } }
ConfigureAwait的作用:使当前async方法的await后续操作不需要恢复到主线程(不需要保存线程上下文)。
异常处理关于新异步里面抛出异常的正确姿势。我们先来看下面一段代码:
button8_Click(object sender, EventArgs e) { Task<string> task = GetUlrStringErr(null); Thread.Sleep(1000);//一段逻辑。。。。 textBox1.Text = await task; } public async Task<string> GetUlrStringErr(string url) { if (string.IsNullOrWhiteSpace(url)) { ); } using (HttpClient http = new HttpClient()) { return await http.GetStringAsync(url); } }
调试执行执行流程:
在执行完118行的时候竟然没有把异常抛出来?这不是逆天了吗。非得在等待await执行的时候才报错,显然119行的逻辑执行是没有什么意义的。让我们把异常提前抛出:
提取一个方法来做验证,这样就能及时的抛出异常了。有朋友会说这样的太坑爹了吧,一个验证还非得另外写个方法。接下来我们提供一个没有这么坑爹的方式:
在异步函数里面用匿名异步函数进行包装,同样可以实现及时验证。
感觉也不比前种方式好多少...可是能怎么办呢。
异步的实现上面简单分析了新异步能力和属性。接下来让我们继续揭秘异步的本质,神秘的外套下面究竟是怎么实现的。
首先我们编写一个用来反编译的示例: