【问题标题】:Integrating async await into synchronous methods将异步等待集成到同步方法中
【发布时间】:2018-05-26 06:41:07
【问题描述】:

我已经用头撞墙两天了,坦率地说,我对自己很生气,因为我似乎无法明白

我在webapi 上下文中。在这个请求期间,我需要向我们的其他系统之一发送一些数据,由于计算量大和多个数据库保存等原因,这个系统返回缓慢。我需要记录这个操作的结果,无论它是否成功或不。但我不想等待它完成。

我有read,我应该从上到下一直是async await。如果我决定这样做,我将不得不转换许多方法,因为我已经深入了 3 或 4 种方法,我担心会扩展更多。

我在这里有什么选择?如果我一直异步等待,我该怎么处理堆栈中更高的方法,比如我的 WebApi 控制器?

这是我的代码,我已经尽可能地精简它。现在我在方法PushResult() 中使用Task.Result()。我的理解是阻止异步?此代码在发送请求时起作用。但是 TestLog 总是最后的,而不是第一个。因此不是异步的。

    //I'm in a public service and referenced twice
    private void MyEndProcess()
    {
        // other stuff

        _vendorPushService.PushResult(); // This could take a while and I have to wait for it!

        _logService.PostLog(LogType.TestLog, "Test");
    }

    //I'm referenced above and somewhere else in the code base
    public void PushResult()
    {   
        ExternalResultModel externalResultModel = _resultService.GetExternalResultModel();

        PushedResultModel pushedResult = new PushedResultModel();

        try
        {
            pushedResult = _vendorRequestService.PushResultAsync(externalResultModel).Result;
        }
        catch (Exception ex)
        {
            pushedResult.Success = false;
        }

        if (pushedResult.Success)
        {
            _logService.PostLog(LogType.SuccessLog, pushedResult.Message);
        }
        else
        {
            _logService.PostLog(LogType.FailedLog, pushedResult.Message);
        }
    }

    public async Task<PushedResultModel> PushResultAsync(ExternalResultModel externalResultModel)
    {
        // setup the requestMessage
        HttpResponseMessage responseMessage = await _httpRequestService
            .SendRequest(requestMessage)
            .ConfigureAwait(false);

        return new PushedResultModel
        {
            Success = responseMessage.IsSuccessStatusCode,
            Message = await responseMessage.Content.ReadAsStringAsync()
        };
    }

    public class HttpRequestService : IHttpRequestService
    {
        private readonly HttpClient _httpClient; 

        public HttpRequestService(IHttpClientAccessor httpClientAccessor)
        {
            _httpClient = httpClientAccessor.HttpClient;
        }

        public async Task<HttpResponseMessage> SendRequest(HttpRequestMessage requestMessage)
        {
            HttpResponseMessage httpResponseMessage = await _httpClient.SendAsync(requestMessage).ConfigureAwait(false);

            return httpResponseMessage;
        }
    }

【问题讨论】:

  • 如果你不愿意一直异步,那么就让一切保持同步,不要在里面放任何异步代码。您只会通过添加一些异步代码而不使整个调用堆栈异步来导致问题
  • 如果您不想等待它完成,那么这本质上是一个即发即弃的场景。这在 web api 上下文中有它自己的问题。我建议您看看这里:blog.stephencleary.com/2014/06/fire-and-forget-on-asp-net.html。在回答您的实际问题方面,您可能想看看ContinueWith,但请先阅读斯蒂芬的帖子。
  • 如果您需要启动后台操作,而不是等待它完成后再将响应返回给客户端 - async await 不会有太大帮助。
  • @Evk 你能扩展一下吗?无论成功/失败,我都需要记录结果。但我不希望用户必须等待它完成。
  • 然后阅读上面的链接,了解asp.net中的fire and forget jobs

标签: c# asp.net-web-api async-await asp.net-4.5


【解决方案1】:

您应该从上到下一直实现异步等待。

如果我一直使用 async await,我该如何处理堆栈中更高的方法,比如我的 WebApi 控制器?

只需像这样使您的控制器操作异步:

[RoutePrefix("api")]
public class PresidentsController : ApiController
{
    [Route("presidents")]
    public async Task<IHttpActionResult> GetPresidents()
    {
        await Task.Delay(TimeSpan.FromSeconds(10)).ConfigureAwait(false);
        return Ok();
    }
}

这是实现异步方法的最简单方法。即使它会增加一些工作以将所有内容更改为异步,它也会在未来受益,因为您将避免异步代码的许多问题。

如果您绝对必须在同步方法中使用异步方法,请将其阻塞在一个地方,如下所示:

public void MySyncMethod()
    {
        try
        {
            this.MyAsyncMethod().Wait();

        }
        catch (Exception exception)
        {
            //omited
        }
    }
private async Task MyAsyncMethod()
{
     await AsyncLogic().ConfigureAwait(false);
}

但我不推荐它。您应该一直使用 async await 到控制器操作。

【讨论】:

【解决方案2】:

在您的评论中,您说您希望在后台处理任务,而不是让调用您的 API 的客户端等待。为此,您实际上并不需要使用 async/await。

试试这个:

private void MyEndProcess()
{
    // other stuff

    Task.Run(_vendorPushService.PushResult()).ConfigureAwait(false); //fire and forget

    _logService.PostLog(LogType.TestLog, "Test");
}

Task.Run 将启动任务,ConfigureAwait(false) 告诉它它不需要在我们当前所在的同一上下文中恢复(这意味着上下文可以在任务完成之前关闭 - 即可以在不等待任务完成的情况下发回响应。

您将收到一个编译器警告,提示您没有在等待 Task.Run,但这正是您想要的。

请记住,当您执行此操作时,HttpContext.Current 将无法在 PushResult 中使用。

【讨论】:

  • ConfigureAwait 在您实际上并没有等待它时毫无意义。配置一个你从不执行的 await 并没有完成任何事情。
  • 您还提倡 OP 使用固有的异步方法,在方法中阻塞结果,然后使用 Task.Run 在线程池线程中启动同步异步包装器。这一切都是一团糟,效率低下。
  • 它阻塞了一个线程,是的(直到任务完成为止),但它不会阻塞请求,这正是 OP 正在寻找的。我之前在 Web API 中使用过即发即弃的任务。它有它的危险,但它也有它的位置。 ConfigureAwait 很重要,因为我发现没有它,在任务完成之前响应不会发回,这不是一劳永逸的。
  • 哦,我什至没有了解这样一个事实,即“一劳永逸”在这里是完全不合适的。不,这不是恰当地使用火然后忘记。这恰恰是对它的不恰当使用。当然,如果您确实想要触发并忘记(您不想),那么您不希望同步阻塞异步操作,然后在线程池中执行同步工作线程,只是忽略结果,甚至不关心它是否运行。最后,没有ConfigureAwait 在这里并不重要。 它什么也没做。它只有在有 await 时才会起作用。
  • @GabrielLuci 我确实发现 ConfigureAwait 没有尽可能地记录在案,但它并没有像您认为的那样做。 ConfigureAwait(false) not 的意思是“不要等待这个任务”。这意味着,“如果此任务是 awaited,请不要尝试在捕获的 SynchronisationContext 上恢复。”此外,我们在这里谈论的“上下文”是 SynchronisationContext。正如您所说,这不会“关闭”。也许您将它与 HttpContext 混淆了?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-19
  • 2015-06-09
  • 1970-01-01
  • 2019-05-31
  • 2015-11-09
相关资源
最近更新 更多