即使使用 ConfigureAwait(false) 在 WebAPI 死锁中同步调用异步方法

Posted

技术标签:

【中文标题】即使使用 ConfigureAwait(false) 在 WebAPI 死锁中同步调用异步方法【英文标题】:Synchronously calling async method in WebAPI deadlocks even with ConfigureAwait(false) 【发布时间】:2017-06-21 12:13:17 【问题描述】:

我有 nuget 包 Esri.ArcGISRuntime,我需要在我的 Web API 2 控制器之一中调用方法 QueryTask.ExecuteAsync。没有同步计数器部分,所以在我的库 c# 代码中,我使用了类似

的包装器
    private QueryResult ExecuteSync()
    
        var queryResults = ExecuteAsync();
        queryResults.Wait();
        return queryResults.Result;
    

    private async Task<QueryResult> ExecuteQueryTaskAsync()
    
        var queryTask = new QueryTask(_uri);
        return await queryTask.ExecuteAsync(_query).ConfigureAwait(false);
    

这在我的程序/服务中完美运行。但是在 Web API 2 控制器中以这种方式使用 ExecuteSync 会导致它完全冻结并且永远不会返回响应。

我做了一些研究,相信这里提到了罪魁祸首: http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html

我绝对不想异步使用该函数。上面的函数是如此的核心,并且隐藏在 4 个包装器中,这将是对我的库类的一次重大改革,以冒泡异步方法以支持这个 web api 调用。

我正在寻找解决 Web API 的这种奇怪行为的变通办法/黑客/建议,以允许我同步运行此异步方法而不会出现死锁

【问题讨论】:

你必须使用await,阅读this @AlexeiLevenkov 我假设您的意思是“内部同步上下文”评论,我认为它需要更多内容或成为更多官方答案。该链接并不真正适合我的 web api 控制器示例。仍然不确定该怎么做才能解决 嗯,你已经有 ConfigureAwait(false)... 代码不应该死锁(所以我更改了标题并删除了错误的重复项) - 最好查看死锁发生时线程卡在哪里.还可以尝试非常基本的异步方法 (Task.Delay(1000)) 以确认问题与库无关。 【参考方案1】:

我绝对不想异步使用这个函数。

不过,我不得不说。异步代码是最好的解决方案。您正在执行的操作是异步的,并且为其公开同步 API 充其量是有问题的。

需要时间吗?当然。但是你的代码会更好。

我正在寻找解决方法/黑客/建议

我有an entire article on the subject of brownfield async development,在那里我介绍了所有已知的黑客及其缺点。

在您的特定情况下(从非核心 ASP.NET 上的 WebApi 调用 并且 考虑到它确实可以从控制台/Win32Service 样式的应用程序中工作),我认为线程池黑客应该为你工作。它看起来像这样:

private QueryResult ExecuteSync()

  return Task.Run(() => ExecuteAsync()).GetAwaiter().GetResult();

这个想法是ExecuteAsync 在请求上下文之外的线程池线程上运行。然后请求线程被阻塞,直到异步工作完成。

【讨论】:

线程池破解成功了。它导致我的控制器上的 customAttribute 出现一个奇怪的问题,但在删除该属性后它起作用了。我会将此作为快速修复推荐给任何人,直到您找到向上异步的时间(我必须在最后期限之前完成,所以这可以节省我的时间)。谢谢 这不能用 ExecuteAsync().ConfigureAwait(false).GetAwaiter().GetResult() 来完成吗? @victor:不,我解释一下why in my article。简而言之,ConfigureAwait(false) 在那里什么都不做,因为没有要配置的await 很奇怪。我有一个到处使用 ConfigureAwait(false) 的 API,当我需要同步运行时,我只需执行 var X = FuncAsync().ConfigureAwait(false).GetAwaiter().GetResult();,它不会在 ASP 中死锁;它运作良好。 @victor:如果可行,那么var X = FuncAsync().GetAwaiter().GetResult(); 也可以。【参考方案2】:

你调用 .Wait() 的对象也是一个任务,你还没有配置它。将 .ConfigureAwait(false) 添加到 ExecuteQueryAsync 的返回值中。

或者,更好的是,WebAPI 也可以是同步的,因此总体上使整个堆栈异步会更好。

【讨论】:

以上是关于即使使用 ConfigureAwait(false) 在 WebAPI 死锁中同步调用异步方法的主要内容,如果未能解决你的问题,请参考以下文章

为整个项目/dll 设置 ConfigureAwait(false)

您可以在没有线程安全的情况下使用 ConfigureAwait(false) 吗?

***请求上的 ConfigureAwait(false)

ConfigureAwait(false) 与将同步上下文设置为 null

第三方使用的 Azure 函数 REST API - 是不是需要使用 ConfigureAwait(false)?

ConfigureAwait(false) 不会使延续代码在另一个线程中运行