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

6

我有一个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调用而将异步方法向上冒泡将是库类的重大改进。

我正在寻找解决此奇怪行为的变通方法/技巧/建议,以允许我同步运行此异步方法而不会死锁。


你必须使用 await,阅读这篇文章 - Legends
@AlexeiLevenkov,我认为你指的是“内部同步上下文”的评论,在我的理解中,它需要更多内容或者变得更像一个官方答案。链接并不适用于我的Web API控制器示例。我还不确定应该采取什么样的解决办法。 - ParoX
嗯,你已经有了 ConfigureAwait(false)... 代码不应该死锁(所以我改了标题并删除了错误的重复)- 最好查看线程在死锁发生时被卡住的位置。同时尝试非常基本的异步方法(Task.Delay(1000)),以确认问题与库无关。 - Alexei Levenkov
2个回答

15

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

虽然如此,我必须说异步代码是最佳解决方案。你正在进行的操作是异步的,为其提供同步API在最好的情况下也存在问题。

需要时间吗?当然需要。但这样做会让你的代码变得更优秀。

我正在寻找解决方法/技巧/建议

我有一篇关于既有异步开发的主题的完整文章,其中涵盖了所有已知的技巧及其缺点。

对于你的特定情况(从WebApi调用非Core ASP.NET,并且考虑到它可以从Console / Win32Service-style应用程序工作),我认为线程池技巧应该适用于你。它看起来像这样:

private QueryResult ExecuteSync()
{
  return Task.Run(() => ExecuteAsync()).GetAwaiter().GetResult();
}

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


线程池的hack起作用了。它在我的控制器上引起了一个奇怪的问题,但是在删除该属性后它就可以工作了。我建议任何人都可以使用这个快速修复方法,直到你有时间进行异步操作(我必须满足截止日期,所以这个方法为我节省了时间)。谢谢。 - ParoX
1
这个不可以用ExecuteAsync().ConfigureAwait(false).GetAwaiter().GetResult()完成吗? - victor
1
@victor:不需要。我在我的文章中解释了原因。简而言之,ConfigureAwait(false)在这里没有任何作用,因为没有await需要配置。 - Stephen Cleary
奇怪。我有一个使用ConfigureAwait(false)的API,当我需要同步运行时,我只需执行var X = FuncAsync().ConfigureAwait(false).GetAwaiter().GetResult();,它不会在ASP中死锁;它可以正常工作。 - victor
@victor:如果这样可行的话,那么 var X = FuncAsync().GetAwaiter().GetResult(); 也是可行的。 - Stephen Cleary

0

你正在调用的对象也是一个任务,并且你还没有对它进行配置。在ExecuteQueryAsync的返回值中添加.ConfigureAwait(false)。

或者,更好的方法是,WebAPI也可以是同步的,因此将整个堆栈异步化将更好。


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