C# HttpClient发送请求不等待响应的方法

8
我正在我的asp.net web api 2应用程序中使用HttpClient类来将一些信息发布到终端点。我只想发布信息而不必等待响应。这是正确的语法吗?
using (var client = new HttpClient())
{
    client.BaseAddress = new Uri("http://localhost:9000/");
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    // HTTP POST
    var gizmo = new Product() { Name = "Gizmo", Price = 100, Category = "Widget" };
    var response = await client.PostAsJsonAsync("api/products", gizmo);
}

你尝试过去掉“await”吗?虽然会有警告,但也许这正是你想要的。 - marco.marinangeli
你是从ASP.NET WebApi应用程序中调用HttpClient吗? - Yuval Itzchakov
@marco.marinangeli:我想知道我是否做得正确?如果我删除await关键字,我会得到以下消息:“因为此调用未被等待,所以当前方法的执行将在调用完成之前继续进行”。 - SharpCoder
你做得很正确,@SharpCoder。记住,如果在函数中使用await,则函数签名的返回类型应为async Task<Type>。再次强调,async并不会使您的执行上下文并行化,而是不会阻塞您端点的I/O操作。这取决于要放入此API调用的负载。否则,无需使用async/await。 - Gomes
@SharpCoder,如果你在谈论fire and forget,那么这就是一个API设计问题。目标API应该被设计成接受请求并将其放入某个队列中,并使用状态码202立即回复。稍后在客户端代码中,您可以使用另一个Get调用来轮询该API的结果。 - Gomes
显示剩余4条评论
2个回答

4

我只想发布信息而不等待响应

在WebAPI中不等待异步方法会导致运行时异常,因为 AspNetSynchronizationContext 知道任何触发的异步操作。如果它注意到控制器操作在异步操作完成之前完成,它将触发所述异常。更多信息请参见 ASP.NET 控制器:异步模块或处理程序在仍有未决异步操作时完成

如果您想使用“火并忘”的语义,如果您正在使用.NET 4.5.2及以上版本,则需要通过 HostingEnvironment.QueueBackgroundWorkItem 排队委托。否则,您可以使用 BackgroundTaskManager

请记住,这种设计并不适合WebAPI。如果您经常触发此操作调用,则它无法扩展。如果这种情况经常发生,请考虑使用更适合的东西,如消息代理。

1
这不是对问题的回答。可以只向服务器POST并终止HTTP / TCP连接。服务器通常会继续处理请求。如果PostAsync始终读取响应,则可能不是解决方案。 - makhdumi
@makhdumi 这不是我想说的。由于 OP 想要从 ASP.NET 执行此操作,他需要知道运行时会注意到未处理的任务,并导致响应返回错误代码而不是正确完成。 - Yuval Itzchakov
3
OP说:“我只想发布信息而不需要等待响应。” 这里的问题是HttpClient.PostAsync将自动读取响应流。如果一个人只想发送数据而不想费心阅读响应,则实际上并不需要有任何操作(异步或同步)来读取请求流(即等待服务器响应)。这与使用QueueBackgroundWorkItem进行“fire and forget”是不同的。你的回答完全有道理,你关于可扩展性的观点完全成立,即使只发送响应,但这并不是问题的真正答案。 - makhdumi
@makhdumi OP 表示“不等待响应”,而不是 “不阅读响应”。你有什么替代建议吗? - Yuval Itzchakov
@makhdumi 当使用 PostAsync 时,唯一的开销是在 IO 完成线程上的回调,这应该不会太耗时。我绝对不会将其称为规模问题,而是微小优化。 - Yuval Itzchakov
显示剩余3条评论

0

要在ASP.NET中实现异步任务,请参考以下示例语法:

    protected void Page_Load(object sender, EventArgs e)
    {
        try
        {
            RegisterAsyncTask(new PageAsyncTask(LoadUrlContent));
        }
        catch {}
    }

    protected async Task LoadUrlContent()
    {
        try
        {
        // Add your code here, for example read the content using HttpClient:
        string _content = await ReadTextAsync(YourUrl, 10);
        }
        catch { throw; }
    }

此外,在页面级别设置<%@ Page ... Async="true" %>
以下示例代码片段展示了HttpClient的使用(从LoadUrlContent()调用此示例函数):
protected async Task<string> ReadTextAsync(string Url, int TimeOutSec)
{
    try
    {
        using (HttpClient _client = new HttpClient() { Timeout = TimeSpan.FromSeconds(TimeOutSec) })
        {
            _client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("text/html"));
            using (HttpResponseMessage _responseMsg = await _client.GetAsync(Url))
            {
                using (HttpContent content = _responseMsg.Content)
                {
                    return await content.ReadAsStringAsync();
                }
            }
        }
    }
    catch { throw; }
}

您可以根据您的特定任务修改此代码库。

希望这可以帮到您。


我的应用程序是asp.net web api。我正在使用HttpClient来发布信息。您提供的答案没有使用HttpClient类。 - SharpCoder
请参考使用HttpClient的扩展答案和示例代码。最好的问候。 - Alexander Bell
谢谢您的快速回复。但我不想阅读响应。我只想点火并忘记。此外,我正在从一个asp.net web api应用程序调用另一个asp.net web api应用程序。 - SharpCoder
那么您可以尝试使用Task.Run(),因为async/await与“fire and forget”有些相反(根据定义,它正在等待某些东西)。不过,您可以使用ConfigureAwait(false)来实现理想的结果。此致最好的问候。 - Alexander Bell
ConfigureAwait在ASP上可能导致死锁。 - user2455808

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接