C# ';等待&x27;工作正常,但调用task.Result会挂起/死锁

C# ';等待&x27;工作正常,但调用task.Result会挂起/死锁,c#,nunit,task,deadlock,async-await,C#,Nunit,Task,Deadlock,Async Await,我有以下四个测试,最后一个测试在运行时挂起。为什么会发生这种情况: [Test] public void CheckOnceResultTest() { Assert.IsTrue(CheckStatus().Result); } [Test] public async void CheckOnceAwaitTest() { Assert.IsTrue(await CheckStatus()); } [Test] public async void CheckStatusTw

我有以下四个测试,最后一个测试在运行时挂起。为什么会发生这种情况:

[Test]
public void CheckOnceResultTest()
{
    Assert.IsTrue(CheckStatus().Result);
}

[Test]
public async void CheckOnceAwaitTest()
{
    Assert.IsTrue(await CheckStatus());
}

[Test]
public async void CheckStatusTwiceAwaitTest()
{
    Assert.IsTrue(await CheckStatus());
    Assert.IsTrue(await CheckStatus());
}

[Test]
public async void CheckStatusTwiceResultTest()
{
    Assert.IsTrue(CheckStatus().Result); // This hangs
    Assert.IsTrue(await CheckStatus());
}

private async Task<bool> CheckStatus()
{
    var restClient = new RestClient(@"https://api.test.nordnet.se/next/1");
    Task<IRestResponse<DummyServiceStatus>> restResponse = restClient.ExecuteTaskAsync<DummyServiceStatus>(new RestRequest(Method.GET));
    IRestResponse<DummyServiceStatus> response = await restResponse;
    return response.Data.SystemRunning;
}

为什么上一个测试挂起?

您遇到了我描述的标准死锁情况:
async
方法试图将其继续调度到被调用
Result
阻塞的线程上


在这种情况下,NUnit使用
SynchronizationContext
执行
async void
测试方法。我会尝试使用
async Task
测试方法。

您遇到了我描述的标准死锁情况:
async
方法试图将其继续调度到被调用
Result
阻塞的线程上


在这种情况下,NUnit使用
SynchronizationContext
执行
async void
测试方法。我将尝试使用
异步任务
测试方法。

您可以避免死锁,将
配置等待(false)
添加到此行:

IRestResponse<DummyServiceStatus> response = await restResponse;
IRestResponse response=wait response;
=>

IRestResponse response=wait response.configurewait(false);

我已经在我的博客文章中描述了这个陷阱

您可以避免死锁将
ConfigureAwait(false)
添加到此行:

IRestResponse<DummyServiceStatus> response = await restResponse;
IRestResponse response=wait response;
=>

IRestResponse response=wait response.configurewait(false);

我在我的博客文章中描述了这个陷阱,通过异步方法获取值:

var result = Task.Run(() => asyncGetValue()).Result;
同步调用异步方法

Task.Run( () => asyncMethod()).Wait();

使用Task.Run不会出现死锁问题。

通过异步方法获取值:

var result = Task.Run(() => asyncGetValue()).Result;
同步调用异步方法

Task.Run( () => asyncMethod()).Wait();

使用Task.Run不会出现死锁问题。

您正在使用Task.Result属性阻止UI。 他们在报告中明确提到,

“Result属性是一个阻塞属性。如果您试图访问它 在其任务完成之前,当前处于活动状态的线程 在任务完成且值可用之前被阻止。在大多数情况下 在这种情况下,应使用AwaitAwait而不是 直接访问该属性。”


此场景的最佳解决方案是从方法中同时删除wait和async,只在返回结果的地方使用Task。它不会扰乱您的执行顺序。

您正在使用Task.Result属性阻塞UI。 他们在报告中明确提到,

“Result属性是一个阻塞属性。如果您试图访问它 在其任务完成之前,当前处于活动状态的线程 在任务完成且值可用之前被阻止。在大多数情况下 在这种情况下,应使用AwaitAwait而不是 直接访问该属性。”


此场景的最佳解决方案是从方法中同时删除wait和async,只在返回结果的地方使用Task。它不会打乱您的执行顺序。

如果您没有收到任何回调或控件挂起,在调用服务/API异步函数后,您必须配置上下文以在相同的被调用上下文上返回结果

使用
TestAsync().ConfigureAwait(continueOnCapturedContext:false)


您将只在web应用程序中遇到此问题,而不是在
static void main

中遇到此问题。如果您没有收到任何回调或控件挂起,则在调用service/API async函数后,您必须配置上下文以返回同一调用上下文的结果

使用
TestAsync().ConfigureAwait(continueOnCapturedContext:false)


您将只在web应用程序中遇到此问题,而不是在@hermanschoonfeld给出的答案的附加部分中遇到此问题。不幸的是,下面的引语不正确:

使用Task.Run不会出现死锁问题

执行被包装在Task.Run中,这将在线程池上调度任务并阻止调用线程。这没关系,只要调用线程不是线程池线程。如果调用线程来自线程池,则会发生以下灾难:一个新任务将排队到队列的末尾,而最终将执行该任务的线程池线程将被阻塞,直到该任务执行为止

在库代码中没有简单的解决方案,因为您无法假定在什么上下文下调用代码。最好的解决方案是只从异步代码中调用异步代码,阻止同步方法中的同步API,不要混合使用它们

资料来源:


对@hermanschoonfeld给出的答案的补充。不幸的是,下面的引语不正确:

使用Task.Run不会出现死锁问题

执行被包装在Task.Run中,这将在线程池上调度任务并阻止调用线程。这没关系,只要调用线程不是线程池线程。如果调用线程来自线程池,则会发生以下灾难:一个新任务将排队到队列的末尾,而最终将执行该任务的线程池线程将被阻塞,直到该任务执行为止

在库代码中没有简单的解决方案,因为您无法假定在什么上下文下调用代码。最好的解决方案是只从异步代码中调用异步代码,阻止同步方法中的同步API,不要混合使用它们

资料来源:


您应该避免从异步方法返回void。它仅用于与现有事件处理程序(主要是在接口代码中)的向后兼容性。如果您的异步方法不返回任何内容,它应该返回任务。我有很多公关
public String GetSqlConnString(RubrikkUser user, RubrikkDb db) 
{ 
    // deadlock if called from threadpool, 
    // works fine on UI thread, works fine from console main 
    return Task.Run(() => 
        GetSqlConnStringAsync(user, db)).Result; 
}