“等待”有效,但调用任务.结果挂起/死锁
本文关键字:结果 任务 挂起 死锁 调用 等待 有效 | 更新日期: 2023-09-27 18:19:24
我有以下四个测试,最后一个在我运行时挂起。为什么会这样:
[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; }
}
为什么最后一个测试挂起?
通过异步方法获取值:
var result = Task.Run(() => asyncGetValue()).Result;
同步调用异步方法
Task.Run( () => asyncMethod()).Wait();
不会因使用 Task.Run 而发生死锁问题。
您遇到了我在博客和 MSDN 文章中描述的标准死锁情况:async
方法试图将其延续安排到被调用 Result
阻止的线程上。
在这种情况下,您的SynchronizationContext
是 NUnit 用来执行async void
测试方法的。我会尝试使用async Task
测试方法。
您可以避免死锁,将ConfigureAwait(false)
添加到此行:
IRestResponse<DummyServiceStatus> response = await restResponse;
=>
IRestResponse<DummyServiceStatus> response = await restResponse.ConfigureAwait(false);
我已经在我的博客文章 async/await 的陷阱中描述了这个陷阱
通过使用 Task.Result 属性阻止 UI。在 MSDN 文档中,他们明确提到,
">结果属性是一个阻止属性。如果您尝试访问它 在其任务完成之前,当前处于活动状态的线程是 阻止,直到任务完成且值可用。在大多数 在这种情况下,您应该使用 Await 或 await 而不是 直接进入酒店。
这种情况的最佳解决方案是从方法中删除 await 和 async,并仅使用您要返回结果的任务。它不会打乱您的执行顺序。
@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.Run 中,这将在线程池上调度任务,阻止调用线程。这没关系,只要调用线程不是线程池线程即可。如果调用线程来自线程池,则会发生以下灾难:新任务排队到队列的末尾,并且最终将执行该任务的线程池线程将被阻塞,直到执行该任务。
在库代码中,没有简单的解决方案,因为您无法假设您的代码是在什么上下文下调用的。最好的解决方案是仅从异步代码调用异步代码,阻止同步方法中的同步 API,不要混合使用它们。
源:
https://medium.com/rubrikkgroup/understanding-async-avoiding-deadlocks-e41f8f2c6f5d
如果未收到任何回调或控件挂起,则在调用服务/API 异步函数后,必须将上下文配置为在同一调用上下文上返回结果。
使用TestAsync().ConfigureAwait(continueOnCapturedContext: false);
您将仅在Web应用程序中遇到此问题,而不会在static void main
中遇到此问题。