【问题标题】:Why use HttpClient for Synchronous Connection为什么使用 HttpClient 进行同步连接
【发布时间】:2013-01-04 07:46:00
【问题描述】:

我正在构建一个类库来与 API 交互。我需要调用 API 并处理 XML 响应。我可以看到使用 HttpClient 进行异步连接的好处,但我所做的是纯粹同步的,所以我看不到使用 HttpWebRequest 的任何显着好处。

如果有人能提供任何启示,我将不胜感激。我不是为了新技术而使用新技术的人。

【问题讨论】:

标签: c# asp.net dotnet-httpclient


【解决方案1】:

但我所做的是纯粹同步的

您可以使用HttpClient 来处理同步请求:

using (var client = new HttpClient())
{
    var response = client.GetAsync("http://google.com").Result;

    if (response.IsSuccessStatusCode)
    {
        var responseContent = response.Content; 

        // by calling .Result you are synchronously reading the result
        string responseString = responseContent.ReadAsStringAsync().Result;

        Console.WriteLine(responseString);
    }
}

至于为什么你应该使用HttpClient 而不是WebRequest ,嗯,HttpClient 是新来的,可能包含对旧客户端的改进。

【讨论】:

  • 您对异步方法的同步使用不会潜在地阻塞您的 UI 线程吗?如果您必须使其同步,您可能需要考虑类似string responseString = Task.Run(() => responseContent.ReadAsStringAsync()).Result;
  • @earthling,是的,Task.Run 从 ThreadPool 调用任务,但您在其上调用 .Result,从而扼杀了由此带来的所有好处并阻塞了您调用此 .Result 的线程(通常恰好是主 UI 线程)。
  • 根据这篇文章(blogs.msdn.com/b/pfxteam/archive/2012/04/13/10293638.aspx)这样调用.Result会耗尽线程池并导致死锁。
  • 如果在由new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext()在主 UI 线程上创建的任务中执行,此代码将始终死锁
  • 那么,如何从 UI 线程同步使用 HttpClient?假设我故意想要阻止 UI 线程(或者我正在编写控制台应用程序),直到我得到 HTTP 响应......所以,如果 Wait()、Result() 等可能导致死锁,对于这种没有死锁风险和没有使用WebClient等其他类的明确解决方案是什么?
【解决方案2】:

我会重申 Donny V. 的回答和 Josh 的

“我不使用异步版本的唯一原因是如果我尝试 支持尚未构建的旧版本 .NET 在异步支持中。”

(如果我有声望,请投票。)

我不记得上次是什么时候了,我很感激 HttpWebRequest 抛出状态码 >= 400 的异常。要解决这些问题,您需要立即捕获异常,并将它们映射到一些非代码中的异常响应机制......本身很无聊,乏味且容易出错。无论是与数据库通信,还是实现定制的 Web 代理,“几乎”总是希望 Http 驱动程序只告诉您的应用程序代码返回了什么,并由您决定如何操作。

因此 HttpClient 更可取。

【讨论】:

  • 我很惊讶HttpClient 本身是HttpWebRequest 的包装器(实际上,它在内部捕获了那些WebException 对象并为您转换为HttpResponseMessage)。我原以为完全从头开始构建新客户端会更容易。
  • 有很多很好的理由,比如不想仅仅为了一个非常低级的 http 调用而重写整个代码库,这甚至不是性能关键(但会将异步引入一百万个地方)。
  • 在 .net core 2 中,如果您想使用 DynamicExpressionParser 动态评估表达式,则可能无法使用异步;属性索引器不能使用异步;在我的情况下,我需要动态评估像 "GetDefaultWelcomeMessage[\"InitialMessage\"]" 这样的字符串,其中此方法进行 HttpCall 并且索引语法优于方法语法 "Util.GetDefaultWelcomeMessage(\"InitialMessage\")"跨度>
【解决方案3】:

对于现在遇到此问题的任何人,.NET 5.0 已将同步 Send 方法添加到 HttpClienthttps://github.com/dotnet/runtime/pull/34948

优点以及为什么在这里详细讨论 - https://github.com/dotnet/runtime/issues/32125

因此,您可以使用它来代替SendAsync。例如

public string GetValue()
{
    var client = new HttpClient();
            
    var webRequest = new HttpRequestMessage(HttpMethod.Post, "http://your-api.com")
    {
        Content = new StringContent("{ 'some': 'value' }", Encoding.UTF8, "application/json")
    };

    var response = client.Send(webRequest);

    using var reader = new StreamReader(response.Content.ReadAsStream());
            
    return reader.ReadToEnd();
}

此代码只是一个简化示例,还没有准备好生产。

【讨论】:

    【解决方案4】:
    public static class AsyncHelper  
    {
        private static readonly TaskFactory _taskFactory = new
            TaskFactory(CancellationToken.None,
                        TaskCreationOptions.None,
                        TaskContinuationOptions.None,
                        TaskScheduler.Default);
    
        public static TResult RunSync<TResult>(Func<Task<TResult>> func)
            => _taskFactory
                .StartNew(func)
                .Unwrap()
                .GetAwaiter()
                .GetResult();
    
        public static void RunSync(Func<Task> func)
            => _taskFactory
                .StartNew(func)
                .Unwrap()
                .GetAwaiter()
                .GetResult();
    }
    

    然后

    AsyncHelper.RunSync(() => DoAsyncStuff());
    

    如果您使用该类将异步方法作为参数传递,您可以安全地从同步方法中调用异步方法。

    这里有解释: https://cpratt.co/async-tips-tricks/

    【讨论】:

      【解决方案5】:

      如果您正在构建一个类库,那么您的库的用户可能希望异步使用您的库。我认为这是最大的原因。

      您也不知道您的图书馆将如何使用。也许用户会处理大量的请求,而异步处理将有助于它更快、更高效地执行。

      如果您可以简单地做到这一点,请尽量不要将负担放在您的库用户身上,在您可以为他们处理时尝试使流程异步。

      我不使用异步版本的唯一原因是,如果我试图支持尚未内置异步支持的旧版本 .NET。

      【讨论】:

      • 我明白了,所以把类库做成异步的,让系统用户自己决定是异步使用还是使用await同步使用?
      • erm,await 通过将控制权返回给调用者来帮助使某些调用异步。
      【解决方案6】:

      在我的情况下,接受的答案不起作用。我从没有异步操作的 MVC 应用程序调用 API。

      这就是我设法使它工作的方法:

      private static readonly TaskFactory _myTaskFactory = new TaskFactory(CancellationToken.None, TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default);
      public static T RunSync<T>(Func<Task<T>> func)
          {           
              CultureInfo cultureUi = CultureInfo.CurrentUICulture;
              CultureInfo culture = CultureInfo.CurrentCulture;
              return _myTaskFactory.StartNew<Task<T>>(delegate
              {
                  Thread.CurrentThread.CurrentCulture = culture;
                  Thread.CurrentThread.CurrentUICulture = cultureUi;
                  return func();
              }).Unwrap<T>().GetAwaiter().GetResult();
          }
      

      然后我这样称呼它:

      Helper.RunSync(new Func<Task<ReturnTypeGoesHere>>(async () => await AsyncCallGoesHere(myparameter)));
      

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-02-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-07-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多