zoukankan      html  css  js  c++  java
  • HttpClient在async中产生的代码不执行和堵塞

    折腾好久,最终在这里找到了答案: https://stackoverflow.com/questions/10343632/httpclient-getasync-never-returns-when-using-await-async/10351400#10351400

    先来看看案例一,代码如下:

    public class ValuesController : ApiController
        {
            [Route("getMessage")]
            public string GetMessage()
            {
                NetTask();
                return "Ok";
            }
    
            public async Task NetTask()
            {
                using (HttpClient client = new HttpClient())
                {
                    HttpRequestMessage request = new HttpRequestMessage
                    {
                        Method = HttpMethod.Get
                    };
                    request.RequestUri = new Uri("http://www.baidu.com");
    
                    var response = await client.SendAsync(request);
                    var result = await response.Content.ReadAsStringAsync();
                    Debug.WriteLine(result);
                }
            }
        }

    上面这段代码,当我们在浏览器上访问/getMessage时,永远不会输出Debug.WriteLine(result); 

    猜想是不是NetTask()没有堵塞,导致主线程结束后,NetTask()方法中的异步代码没有执行。

    运行案例二,把NetTask()堵塞掉,代码如下:

    public string GetMessage()
            {
                NetTask().GetAwaiter().GetResult();
                return "Ok";
            }

    访问/getMessage,发现请求得不到响应,断点一下,发现NetTask()方法一直被堵塞了,引发了死锁!

    有的人可能会说GetMessage()应该打上async标记,使用await 等待NetTask,没错,这样实现的话代码是没有任何问题的,但是不满足我们现在的要求,假设NetTask()的网络请求耗时为10秒,如果我们这样去写我们的代码,那GetMessage()这个Action的耗时起码需要10秒。

    也有人会推荐使用Task.Run,用其他线程去执行这个耗时请求。但是这样的操作还是额外产生了线程的调度(主线程,Task.Run中的线程,await后的线程,共3个线程),我们的理想操作是主线程完成请求前的所有动作后不堵塞完成NetTask()后面的代码。请求完成后的操作交给其他线程。这样我们只使用到2个线程。

    Context 上下文

    在我们使用async/await时,我们首先需要了解"Context",简单的说,当我们使用async/await时,当程序遇到需要等待的地方,程序会捕捉到当前的Context,当异步方法完成后,程序会恢复捕捉到的Context,并在上面执行后续的代码。异步的上下文可以分为UI上下文,ASP.NET Request上下文,线程池上下文,让我们用代码解释一下:

    // WinForms example (WPF同原理).
    private async void DownloadFileButton_Click(object sender, EventArgs e)
    {
      // 异步方法,当代码需要await时,程序捕捉当前的上下文,此处为UI上下文。await期间主线程是不会被阻塞的
      await DownloadFileAsync(fileNameTextBox.Text);
    
      // await完成,程序将为我们恢复捕捉的上下文,也就是UI上下文,因为我们拥有UI上下文所以可以更新UI控件,即使当前的线程可能是其他线程
      resultTextBox.Text = "File downloaded!";
    }
    
    // ASP.NET example
    protected async void MyButton_Click(object sender, EventArgs e)
    {
      // 异步方法,当代码需要await时,程序捕捉当前的上下文,此处为请求的上下文。await期间允许线程继续处理其他请求
      await DownloadFileAsync(...);
    
      // await完成,程序恢复request上下文,即便当前操作的可能是其他线程,但我们拥有请求的上下文,所以我们能响应请求。
      Response.Write("File downloaded!");
    }

     那么再回过头来解释一下案例一为什么不能运行到client.SendAsync()后面的代码,按照我们刚才所说,异步时会为我们捕捉请求的上下文。在看NetTask()并没有被阻塞,所以请求马上就结束了。当client.SendAsync()被执行完后无法操作一个已经结束的请求上下文,自然不会执行下面的代码。当然如果client.SendAsync()执行的够快,在请求还没有结束前完成,那么依旧能运行到后续的代码。

    再来解释一下案例二为什么会死锁,在案例二中,使用GetAwaiter().GetResult()阻塞住了主线程,异步帮我们捕捉了请求的上下文,当异步完成恢复请求上下文的环境时,因为主线程阻塞等待异步的完成,而异步线程因为主线程阻塞没办法操作请求上下文,产生了死锁。

    那么应该怎么解决这种问题呢?

     ConfigureAwait

    大部分情况,我们可能不需要返回到"主"上下文中,大部分的异步方法可以被组合使用,每个异步操作可能只代表本身,和之前的上下文并没有什么联系。此时,我们可以通过ConfigureAwait告诉程序不需要捕捉上下文:

    private async Task DownloadFileAsync(string fileName)
    {
      // 使用HttpClient或者其他下载文件
      var fileContents = await DownloadFileContentsAsync(fileName).ConfigureAwait(false);
    
      // 因为配置了ConfigureAwait(false),所以此时不再是之前的上下文,而是Thread pool Context// 将文件写到硬盘
      await WriteToDiskAsync(fileName, fileContents).ConfigureAwait(false);
    
      // 第二个ConfigureAwait不是必须的,但是不错的做法
    }
    
    // WinForms example (it works exactly the same for WPF).
    private async void DownloadFileButton_Click(object sender, EventArgs e)
    {
      // await,UI线程不会在此阻塞,并且调用异步方法没有使用ConfigureAwait(false),所以此时会捕捉UI上下文
      await DownloadFileAsync(fileNameTextBox.Text);
    
      // 我们拥有UI上下文,所以当异步恢复时,可以直接操作UI控件
      resultTextBox.Text = "File downloaded!";
    }

     当然,如果我们操作全程都包括标注了async,也不会出现阻塞的情况,但Action肯定是需要等异步结束后才能返回的。

    参考文章:https://blogs.msdn.microsoft.com/pfxteam/2012/04/12/asyncawait-faq/

        http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html

        http://blog.stephencleary.com/2012/02/async-and-await.html

  • 相关阅读:
    MySQL难点语法——连接
    MySQL难点语法——子查询
    MySQL查询——select
    python高级编程——入门语法(二)
    python高级编程——入门语法(一)
    MySQL语言分类——DML
    python高级编程——锁
    浮点数的秘密
    有符号和无符号数
    基本数据类型
  • 原文地址:https://www.cnblogs.com/niye/p/9288511.html
Copyright © 2011-2022 走看看