·您现在的位置: 云翼网络 >> 文章中心 >> 网站建设 >> 网站建设开发 >> ASP.NET网站开发 >> ASP.NET sync over async(异步中同步,什么鬼?)

ASP.NET sync over async(异步中同步,什么鬼?)

作者:佚名      ASP.NET网站开发编辑:admin      更新时间:2022-07-23

asp.net sync over async(异步中同步,什么鬼?)

async/await 是我们在 ASP.NET 应用程序中,写异步代码最常用的两个关键字,使用它俩,我们不需要考虑太多背后的东西,比如异步的原理等等,如果你的 ASP.NET 应用程序是异步到底的,包含数据库访问异步、网络访问异步、服务调用异步等等,那么恭喜你,你的应用程序是没问题的,但有一种情况是,你的应用程序代码比较老,是同步的,但现在你需要调用异步代码,这该怎么办呢?有人可能会说,很简单啊,不是有个 .Result 吗?但事实真的就这么简单吗?我们来探究下。

首先,放出几篇经典文章:

  • async & await 的前世今生
  • 异步编程 In .NET(中文资料中,写异步最棒的两篇文章
  • HttpClient.GetAsync(…) never returns when using await/async
  • Don't Block on Async Code(下面测试中的第三种情况
  • Should I expose synchronous wrappers for asynchronous methods?(sync over async 透彻

上面文章的内容,我们后面会说。光看不练假把式,所以,如果真正要体会 sync over async,我们还需要自己动手进行测试:

  • 1. 异步调用使用 .Result,同步调用使用 .Result
  • 2. 异步调用使用 await,同步调用使用 Task.Run
  • 3. 异步调用使用 await,同步调用使用 .Result
  • 4. 异步调用使用 Task.Run,同步调用使用 .Result
  • 5. 异步调用使用 await .ConfigureAwait(true),同步调用使用 .Result
  • 6. 异步调用使用 await .ConfigureAwait(false),同步调用使用 .Result
  • 7. 异步调用使用 await,异步调用使用 await
  • 8. 测试总结

先说明一下,在测试代码中,异步调用使用的是 HttpClient.GetAsync 方法,并且测试请求执行两次,关于具体的分析,后面再进行说明。

1. 异步调用使用 .Result,同步调用使用 .Result

测试代码:

[Route("")][HttpGet]public string Index(){    System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId1:" + Thread.CurrentThread.ManagedThreadId);    var result = Test();    System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId4:" + Thread.CurrentThread.ManagedThreadId);    return result;}public static string Test(){    System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId2:" + Thread.CurrentThread.ManagedThreadId);    using (var client = new HttpClient())    {        var response = client.GetAsync(url).Result;        System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId3:" + Thread.CurrentThread.ManagedThreadId);        return response.Content.ReadAsStringAsync().Result;    }}

输出结果:

Thread.CurrentThread.ManagedThreadId1:13Thread.CurrentThread.ManagedThreadId2:13Thread.CurrentThread.ManagedThreadId3:13Thread.CurrentThread.ManagedThreadId4:13Thread.CurrentThread.ManagedThreadId1:6Thread.CurrentThread.ManagedThreadId2:6Thread.CurrentThread.ManagedThreadId3:6Thread.CurrentThread.ManagedThreadId4:6

简单总结:同步代码中调用异步,上面的测试代码应该是我们最常写的,为什么没有出现线程阻塞,页面卡死的情况呢?而且代码中调用了 GetAsync,为什么请求线程只有一个?后面再说,我们接着测试。

2. 异步调用使用 await,同步调用使用 Task.Run

测试代码:

[Route("")][HttpGet]public string Index(){    System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId1:" + Thread.CurrentThread.ManagedThreadId);    var result = Task.Run(() => Test2()).Result;    System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId4:" + Thread.CurrentThread.ManagedThreadId);    return result;}public static async Task<string> Test2(){    System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId2:" + Thread.CurrentThread.ManagedThreadId);    using (var client = new HttpClient())    {        var response = await client.GetAsync(url);        System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId3:" + Thread.CurrentThread.ManagedThreadId);        return await response.Content.ReadAsStringAsync();    }}

输出结果:

Thread.CurrentThread.ManagedThreadId1:6Thread.CurrentThread.ManagedThreadId2:7Thread.CurrentThread.ManagedThreadId3:11Thread.CurrentThread.ManagedThreadId4:6Thread.CurrentThread.ManagedThreadId1:6Thread.CurrentThread.ManagedThreadId2:7Thread.CurrentThread.ManagedThreadId3:12Thread.CurrentThread.ManagedThreadId4:6

简单总结:根据上面的输出结果,我们发现,在一个请求过程中,总共会出现三个线程,一个是开始的请求线程,接着是 Task.Run 创建的一个线程,然后是异步方法中 await 等待的执行线程,需要注意的是,ManagedThreadId1 和 ManagedThreadId4 始终是一样的。

3. 异步调用使用 await,同步调用使用 .Result

测试代码:

[Route("")][HttpGet]public string Index(){    System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId1:" + Thread.CurrentThread.ManagedThreadId);    var result = Test3().Result;    System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId4:" + Thread.CurrentThread.ManagedThreadId);    return result;}public static async Task<string> Test3(){    System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId2:" + Thread.CurrentThread.ManagedThreadId);    using (var client = new HttpClient())    {        var response = await client.GetAsync(url);        System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId3:" + Thread.CurrentThread.ManagedThreadId);        return await response.Content.ReadAsStringAsync();    }}

输出结果:

Thread.CurrentThread.ManagedThreadId1:5Thread.CurrentThread.ManagedThreadId2:5

简单总结:首先,页面是卡死状态,ManagedThreadId3 并没有输出,也就是执行到 await client.GetAsync 的时候,线程就阻塞了。

4. 异步调用使用 Task.Run,同步调用使用 .Result

测试代码:

[Route("")][HttpGet]public string Index(){    System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId1:" + Thread.CurrentThread.ManagedThreadId);    var result = Test4().Result;    System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId4:" + Thread.CurrentThread.ManagedThreadId);    return result;}public static async Task<string> Test4(){    System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId2:" + Thread.CurrentThread.ManagedThreadId);    return await Task.Run(() =>    {        Thread.Sleep(1000);        System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId3:" + Thread.CurrentThread.ManagedThreadId);        return "xishuai";    });}

输出结果:

Thread.CurrentThread.ManagedThreadId1:6Thread.CurrentThread.ManagedThreadId2:6Thread.CurrentThread.ManagedThreadId3:7

简单总结:和第三种情况一样,页面也是卡死状态,但不同的是,ManagedThreadId3 是输出的,测试它的主要目的是和第三种情况形成对比,以便了解 HttpClient.GetAsync 中到底是什么鬼?

5. 异步调用使用 await .ConfigureAwait(true),同步调用使用 .Result

测试代码:

[Route("")][HttpGet]public string Index(){    System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId1:" + Thread.CurrentThread.ManagedThreadId);    var result = Test5().Result;    System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId4:" + Thread.CurrentThread.ManagedThreadId);    return result;}public static async Task<string> Test5(){    System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId2:" + Thread.CurrentThread.ManagedThreadId);    using (var client = new HttpClient())    {        var task = client.GetAsync(url);        var response = await task.ConfigureAwait(true);        System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId3:" + Thread.CurrentThread.ManagedThreadId);        return await response.Content.ReadAsStringAsync();    }}

输出结果:

Thread.CurrentThread.ManagedThreadId1:6Thread.CurrentThread.ManagedThreadId2:6

简单总结:和上面两种情况一样,页面也是卡死状态,它的效果和第三种完全一样,ManagedThreadId3 都没有输出的。

6. 异步调用使用 await .ConfigureAwait(false),同步调用使用 .Result

测试代码:

[Route("")][HttpGet]public string Index(){    System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId1:" + Thread.CurrentThread.ManagedThreadId);    var result = Test6().Result;    System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId4:" + Thread.CurrentThread.ManagedThreadId);    return result;}publ