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

转自:http://www.cnblogs.com/xishuai/p/asp-net-sync-over-async.html

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

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

上面文章的内容,我们后面会说。光看不练假把式,所以,如果真正要体会 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:13
Thread.CurrentThread.ManagedThreadId2:13
Thread.CurrentThread.ManagedThreadId3:13
Thread.CurrentThread.ManagedThreadId4:13
Thread.CurrentThread.ManagedThreadId1:6
Thread.CurrentThread.ManagedThreadId2:6
Thread.CurrentThread.ManagedThreadId3:6
Thread.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:6
Thread.CurrentThread.ManagedThreadId2:7
Thread.CurrentThread.ManagedThreadId3:11
Thread.CurrentThread.ManagedThreadId4:6
Thread.CurrentThread.ManagedThreadId1:6
Thread.CurrentThread.ManagedThreadId2:7
Thread.CurrentThread.ManagedThreadId3:12
Thread.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:5
Thread.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:6
Thread.CurrentThread.ManagedThreadId2:6
Thread.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:6
Thread.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;
}

public static async Task<string> Test6()
{
    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(false);
        System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId3:" + Thread.CurrentThread.ManagedThreadId);
        return await response.Content.ReadAsStringAsync();
    }
}

输出结果:

Thread.CurrentThread.ManagedThreadId1:6
Thread.CurrentThread.ManagedThreadId2:6
Thread.CurrentThread.ManagedThreadId3:10
Thread.CurrentThread.ManagedThreadId4:6
Thread.CurrentThread.ManagedThreadId1:8
Thread.CurrentThread.ManagedThreadId2:8
Thread.CurrentThread.ManagedThreadId3:11
Thread.CurrentThread.ManagedThreadId4:8

简单总结:和第五种情况形成对比,仅仅只是把 ConfigureAwait 参数设置为 false,结果却完全不同。

7. 异步调用使用 await,异步调用使用 await

测试代码:

[Route("")]
[HttpGet]
public async Task<string> Index()
{
    System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId1:" + Thread.CurrentThread.ManagedThreadId);
    var result = await Test7();
    System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId4:" + Thread.CurrentThread.ManagedThreadId);
    return result;
}

public static async Task<string> Test7()
{
    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:6
Thread.CurrentThread.ManagedThreadId2:6
Thread.CurrentThread.ManagedThreadId3:12
Thread.CurrentThread.ManagedThreadId4:12
Thread.CurrentThread.ManagedThreadId1:7
Thread.CurrentThread.ManagedThreadId2:7
Thread.CurrentThread.ManagedThreadId3:8
Thread.CurrentThread.ManagedThreadId4:8

简单总结:注意这是异步的写法,调用和被调用方法都是异步的,从输出的结果中,我们就会发现,这种情况和上面的六种情况,有一个最明显的区别就是,请求线程和结束线程不是同一个,说明什么呢?线程是异步等待的。

8. 测试总结

先梳理一下测试结果:

  1. 异步调用使用 .Result,同步调用使用 .Result:通过,始终一个线程。
  2. 异步调用使用 await,同步调用使用 Task.Run:通过,三个线程,请求开始和结束为相同线程。
  3. 异步调用使用 await,同步调用使用 .Result:卡死,线程阻塞。
  4. 异步调用使用 Task.Run,同步调用使用 .Result:卡死,线程阻塞。
  5. 异步调用使用 await .ConfigureAwait(true),同步调用使用 .Result:卡死,线程阻塞。
  6. 异步调用使用 await .ConfigureAwait(false),同步调用使用 .Result:通过,两个线程,await 执行为单独一个线程。
  7. 异步调用使用 await,异步调用使用 await:通过,两个线程,请求开始和结束为不同线程。

上面这么多的测试情况,看起来可能有些晕,我们先从最简单的第二种情况开始分析下,首先,页面是同步方法,请求线程可以看作是一个主线程 1,然后通过 Task.Run 创建线程 2,让它去执行 Test2 方法,需要注意的是,这时候主线程 1 并不会往下执行(从输出结果可以看出),它会等待线程 2 执行,主要是等待线程 2 执行返回结果,在 Test2 方法中,一切是异步方法,await client.GetAsync 会创建又一个线程 3 去执行,并且线程 2 等待它返回结果,然后最终回到线程 1 上,在整个过程中,虽然有三个线程,但这三个线程并不是同时工作的,而是一个执行之后等待另一个执行的结果,所以整个执行过程还是同步的。

第三种和第二种情况的不同就是,异步调用由 Task.Run 改成了 .Result,然后就造成了页面卡死,在 Don‘t Block on Async Code 这篇文章中,就是详细说明的这种情况,为什么会卡死呢?其实你从同样卡死的第四种情况和第五种情况中,可以发现一些线索,ConfigureAwait 的说明是:试图继续回夺取的原始上下文,则为 true;否则为 false。什么意思呢?就是它可以变身为请求线程,最能体现出这一点的是,如果设置为 true,那么在这个线程中,就可以访问 HttpContext.Current,那为什么在同步调用中,设置为 true 就造成页面卡死呢?我们分析一下,页面是同步方法,请求线程可以看作是一个主线程 1,然后调用 Test3 异步方法,这时候主线程 1,会在这里等待异步的执行结果,在 Test3 方法中创建一个线程 2,因为把 ConfigureAwait 设置为了 true,那么线程 2 就想把自己变身成为请求线程(谋权篡位),也就是线程 1,但是人家线程 1 现在正在门口等它呢?线程 2 却想占有线程 1 的地位,很显然,这是不成功的,那什么情况下可以谋权篡位成功呢?就是线程 1 不在,也就是线程 1 回到线程池中了,这就是异步等待的效果,也是它的威力。

针对第三种情况,简单画了一个示意图:

在第五种情况中,因为把 ConfigureAwait 设置为 false,线程 2 不想谋权篡位了,它只想老老实实的做事,把执行结果返回给请求线程 1,那么整个请求执行过程就是顺利的。

同步调用异步测试中,还剩一个第一种情况,它和其他情况不同的是,没有异步方法,只是使用的是 .Result,那为什么它是通过的?并且线程始终是一个呢?首先,页面请求开始,创建一个请求线程 1,因为 Test 方法并不是异步方法,所以还是线程 1 去执行它,执行到了 client.GetAsync 这一步,因为没有使用 await,所以并不会创建一个线程去执行它,并且最终的是,虽然 GetAsync 是异步方法,但再其实现代码中,设置了 ConfigureAwait(false):

async Task<HttpResponseMessage> SendAsyncWorker(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
{
    using (var lcts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken))
    {
        lcts.CancelAfter(timeout);

        var task = base.SendAsync(request, lcts.Token);
        if (task == null)
            throw new InvalidOperationException("Handler failed to return a value");

        var response = await task.ConfigureAwait(false);//重点
        if (response == null)
            throw new InvalidOperationException("Handler failed to return a response");

        //
        // Read the content when default HttpCompletionOption.ResponseContentRead is set
        //
        if (response.Content != null && (completionOption & HttpCompletionOption.ResponseHeadersRead) == 0)
        {
            await response.Content.LoadIntoBufferAsync(MaxResponseContentBufferSize).ConfigureAwait(false);
        }

        return response;
    }
}

所以,整个过程应该是这样的,在测试代码中始终是一个请求线程在执行,并且在 client.GetAsync 的执行中,会创建另外一个线程 2 去执行,然后线程 1 等待线程 2 的执行结果,因为 GetAsync 的实现并不在测试代码中,所以表现出来就是一个线程在执行,虽然是异步方法,但它和同步方法一样,为什么?因为线程始终在等待另一个线程的执行结果,也就是说,在某一时刻,始终是一个线程在执行,其余线程都在等待。

sync over async(异步中同步)是否可行?通过上面的测试结果可以得出是可行的,但要注意一些写法问题:

  • 异步调用使用 .Result,而不能出现 await。
  • 不能出现 ConfigureAwait(true)。
  • 可以使用 Task.Run,但仅限于不返回结果的执行线程。

当然最好的方式是异步到底

原文地址:https://www.cnblogs.com/dongh/p/9728108.html

时间: 2024-09-29 20:41:13

ASP.NET sync over async(异步中同步,什么鬼?)的相关文章

Async 异步转同步详细流程解释

安装 npm install async --save 地址 https://github.com/caolan/async Async的内容主要分为三部分 流程控制: 简化九种常见的流程的处理 集合处理:如何使用异步操作处理集中的数据 工具类:几个常用的工具类 本文主要介绍流程控制部分,后续内容持续更新,由于node.js是异步编程模型,有许多在同步编程中很容易做到的事情,现在就会变的很麻烦,并且存在很多的callback.但是,Async的流程控制给我们coder带来了许多便利. 1.ser

半同步半异步I/O的设计模式(half sync/half async)

半同步半异步I/O的设计模式(half sync/half async) c++实现半同步半异步I/O的设计模式(half sync/half async)

ajax中的async属性值之同步和异步及同步和异步区别

在Jquery中ajax方法中async用于控制同步和异步,当async值为true时是异步请求,当async值为fase时是同步请求.ajax中async这个属性,用于控制请求数据的方式,默认是true,即默认以异步的方式请求数据. jquery中ajax方法有个属性async用于控制同步和异步,默认是true,即ajax请求默认是异步请求,有时项目中会用到AJAX同步.这个同步的意思是当JS代码加载到当前AJAX的时候会把页面里所有的代码停止加载,页面出现假死状态,当这个AJAX执行完毕后才

【iOS面试系列-2】多线程中同步、异步和串行、并行之间的逻辑关系(必考,必须掌握)

一.同步.异步和串行.并行 任务串行执行就是每次只有一个任务被执行,任务并发执行就是在同一时间可以有多个任务被执行. 一个同步函数只在完成了它预定的任务后才返回.一个异步函数,刚好相反,会立即返回,预定的任务会完成但不会等它完成.因此,一个异步函数不会阻塞当前线程去执行下一个函数. (来源:http://www.cocoachina.com/industry/20140428/8248.html) 队列分为串行和并行 任务的执行分为同步和异步 -------  队列只是负责任务的调度,而不负责任

ASP.NET WebForm中用async/await实现异步出人意料的简单

1. 在.aspx中添加异步标记 <%@ Page Language="C#" Async="true"%> 2. 在.aspx.cs或者.ascx.cs(用户控件)中添加异步方法 private async Task GetMyPosts() { var posts = await ServiceFactory.BlogPostSevice.GetBlogPostsPagedAsync(); rpPosts.DataSource = posts; rp

IO中同步异步,阻塞与非阻塞 -- 原理篇

再补一篇高手写的理论分析,便于更深刻理解 转自:http://blog.csdn.net/historyasamirror/article/details/5778378 ============================================================= 同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO分别是什么,到底有什么区别?这个问题其实不同的人给出的答

IO中同步异步,阻塞与非阻塞

一.同步与异步 同步/异步, 它们是消息的通知机制 1. 概念解释 A. 同步 所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回. 按照这个定义,其实绝大多数函数都是同步调用(例如sin isdigit等). 但是一般而言,我们在说同步.异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务. 最常见的例子就是 SendMessage. 该函数发送一个消息给某个窗口,在对方处理完消息之前,这个函数不返回. 当对方处理完毕以后,该函数才把消息处理函数所返回的值返回给调

IO中同步、异步与阻塞、非阻塞的区别(转)

转自:http://blog.chinaunix.net/uid-26000296-id-3754118.html 一.同步与异步同步/异步, 它们是消息的通知机制 1. 概念解释A. 同步所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回. 按照这个定义,其实绝大多数函数都是同步调用(例如sin isdigit等).但是一般而言,我们在说同步.异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务.最常见的例子就是 SendMessage.该函数发送一个消息给某个窗

IO中同步、异步与阻塞、非阻塞的区别

一.同步与异步同步/异步, 它们是消息的通知机制 1. 概念解释A. 同步所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回. 按照这个定义,其实绝大多数函数都是同步调用(例如sin isdigit等).但是一般而言,我们在说同步.异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务.最常见的例子就是 SendMessage.该函数发送一个消息给某个窗口,在对方处理完消息之前,这个函数不返回.当对方处理完毕以后,该函数才把消息处理函数所返回的值返回给调用者. B.