‘ wait’可以工作,但调用 task。结果挂起/死锁

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

[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;
}

我对 RestSharp RestClient使用这种扩展方法:

public static class RestClientExt
{
public static Task<IRestResponse<T>> ExecuteTaskAsync<T>(this RestClient client, IRestRequest request) where T : new()
{
var tcs = new TaskCompletionSource<IRestResponse<T>>();
RestRequestAsyncHandle asyncHandle = client.ExecuteAsync<T>(request, tcs.SetResult);
return tcs.Task;
}
}
public class DummyServiceStatus
{
public string Message { get; set; }
public bool ValidVersion { get; set; }
public bool SystemRunning { get; set; }
public bool SkipPhrase { get; set; }
public long Timestamp { get; set; }
}

为什么最后一个测试挂起来了?

87574 次浏览

您正在遇到我所描述的 在我的博客上在一篇 MSDN 文章里的标准死锁情况: async方法正试图将其延续安排到被 Result调用阻塞的线程上。

在本例中,您的 SynchronizationContext是 NUnit 用来执行 async void测试方法的 SynchronizationContext。我会尝试使用 async Task测试方法来代替。

可以避免将 ConfigureAwait(false)添加到这一行时出现死锁:

IRestResponse<DummyServiceStatus> response = await restResponse;

=>

IRestResponse<DummyServiceStatus> response = await restResponse.ConfigureAwait(false);

我在我的博客文章 异步/等待的缺陷中描述了这个陷阱

通过异步方法获取值:

var result = Task.Run(() => asyncGetValue()).Result;

同步调用异步方法

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

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

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

结果属性是一个阻塞属性。如果尝试访问该属性,则 在其任务完成之前,当前处于活动状态的线程是 阻塞,直到任务完成并且该值可用。在大多数 例中,您应该使用 等等等待而不是 直接进入房产”

对于这种情况,最好的解决方案是从方法中使用 删除等待和异步,并且只在返回结果的地方使用 任务。不会影响你的执行顺序。

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

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

您将只在 Web 应用程序中面临这个问题,而在 static void main中不会遇到这个问题。

这是对@HermanSchoenfeld 的回答的补充。不幸的是,下面的引用并不正确:

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

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;
}

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

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

来源:

Https://medium.com/rubrikkgroup/understanding-async-avoiding-deadlocks-e41f8f2c6f5d