142

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

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

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

4

6 回答 6

252

通过异步方法获取值:

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

同步调用异步方法

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

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

于 2015-09-07T00:46:51.160 回答
98

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

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

于 2013-06-22T08:30:52.880 回答
18

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

IRestResponse<DummyServiceStatus> response = await restResponse;

=>

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

我已经在我的博文 Pitfalls of async/await中描述了这个陷阱

于 2014-12-20T15:05:49.007 回答
9

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

Result属性是一个阻塞属性。如果您在其任务完成之前尝试访问它,当前处于活动状态的线程将被阻塞,直到任务完成并且该值可用。在大多数情况下,您应该使用Await访问该值或等待而不是直接访问该属性。”

这种情况的最佳解决方案是从方法中删除 await 和 async并仅使用返回结果的Task 。它不会弄乱你的执行顺序。

于 2015-10-30T11:20:02.290 回答
2

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

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

您只会在 Web 应用程序中遇到此问题,而不会在static void main.

于 2017-11-03T10:59:26.520 回答
2

@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

于 2021-02-08T09:39:34.510 回答