【问题标题】:What's the "right way" to use HttpClient synchronously?同步使用 HttpClient 的“正确方法”是什么?
【发布时间】:2018-11-28 22:29:41
【问题描述】:

我在“正确方式”周围使用了引号,因为我已经很清楚使用异步 API 的正确方式是让异步行为在整个调用链中传播。这不是一个选项。

我正在处理一个非常庞大且复杂的系统,该系统专门设计用于在循环中同步进行批处理。

我突然使用 HttpClient 的原因是因为之前批处理的所有数据都是从 SQL 数据库中收集的,现在我们正在添加一个 Web API 调用。

是的,我们正在同步执行循环中调用 Web API。我知道。将整个事情重写为异步并不是一种选择。这实际上是我们想要做的。 (我们正在尽可能减少 API 调用的数量)

我实际上确实尝试将异步行为传播到调用链上,但后来我发现自己有 50 个文件发生了深刻的变化,仍然有数百个编译器错误需要解决,并且失去了所有希望。我被打败了。

那么,回到这个问题,鉴于微软建议不要使用 WebRequest 进行新开发,而是使用仅提供异步 API 的 HttpClient,我该怎么办?

这是我正在做的一些伪代码......

foreach (var thingToProcess in thingsToProcess)
{
    thingToProcess.ProcessStuff(); // This makes an API call
}

如何实现 ProcessStuff()?

我的第一个实现是这样的

public void ProcessStuff()
{
    var apiResponse = myHttpClient // this is an instance of HttpClient
        .GetAsync(someUrl)
        .Result;

    // do some stuff with the apiResponse
}

然而,有人告诉我,以这种方式调用 .Result 可能会在从 ASP.NET 之类的东西调用时由于同步上下文而导致死锁。

猜猜看,这个批处理将从 ASP.NET 控制器启动。是的,我知道,这很愚蠢。当它从 ASP.NET 运行时,它只是“批处理”一个项目而不是整个批次,但我离题了,它仍然从 ASP.NET 调用,因此我担心死锁。

那么处理这个问题的“正确方法”是什么?

【问题讨论】:

标签: c# asynchronous async-await


【解决方案1】:

尝试以下方法:

var task = Task.Run(() => myHttpClient.GetAsync(someUrl)); 
task.Wait();
var response = task.Result;

仅当您无法使用async 方法时才使用它。

如 MSDN 博客中所述,此方法完全没有死锁: ASP.Net–Do not use Task .Result in main context.

【讨论】:

  • 感谢您最终以 MSDN 帖子的形式对此提供了明确的答案。实际上,我在发布之前在 Google 上搜索了这个问题,并在 StackOverflow 上发现了许多类似的问题,但没有一个提供来自 MS 的源代码,明确指出在线程上运行任务、等待并在等待后获取结果是安全的。这个问题上的许多其他 SO 线程实际上将错误答案作为接受的答案,但它看起来与正确答案非常相似。也就是说,他们跳过了对 .Wait() 的调用
  • 这不起作用。 task.Wait(); 上的死锁
  • 2021 更新:请参阅@alexs 的另一个答案,我现在已将其标记为已接受的答案。 HttpClient API 中添加了一个新的同步方法。
  • @spoonraker 我认为这仍然是您提出的具体问题的更好答案,这可能会吸引那些“使用 .NET 5”不是获得一小部分的合理答案的人代码工作。对他们来说,这个问题的基本前提仍然非常重要:没有同步 API,但不推荐使用替代方案。我怀疑许多使用 .NET 5 的人甚至有理由问这个问题。只是一个想法。
  • 对某些人来说可能很明显,但对我来说不是,并且想为其他人注意以防万一-使用 System.Threading.Tasks 添加;到你的代码。我选择了错误的建议参考,并希望避免其他人的困惑。
【解决方案2】:

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

因此,您可以使用它来代替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();
}

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

【讨论】:

  • I cry b/c 我们的企业软件仍然使用 netfx 4.7
【解决方案3】:

您还可以考虑使用 Nito.AsyncEx,它是一个 nuget 包。我听说过使用 Task.Run() 的问题,这解决了这个问题。这是 api 文档的链接: http://dotnetapis.com/pkg/Nito.AsyncEx/4.0.1/net45/doc/Nito.AsyncEx.AsyncContext

下面是在控制台应用程序中使用异步方法的示例: https://blog.stephencleary.com/2012/02/async-console-programs.html

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-18
    • 1970-01-01
    相关资源
    最近更新 更多