await工作但调用task.Result挂起/死锁

时间:2013-06-22 08:10:58

标签: 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 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; }
}

为什么最后一次测试会挂起?

6 个答案:

答案 0 :(得分:194)

通过异步方法获取值:

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

同步调用异步方法

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

由于使用Task.Run,​​不会发生死锁问题。

答案 1 :(得分:75)

您正在遇到我描述的标准死锁情况on my blogin an MSDN articleasync方法正在尝试将其继续安排到被阻止的线程上致电Result

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

答案 2 :(得分:15)

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

IRestResponse<DummyServiceStatus> response = await restResponse;

=&GT;

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

我在博文Pitfalls of async/await

中描述了这个陷阱

答案 3 :(得分:8)

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

  

结果属性是阻止属性。如果您尝试访问它   在任务完成之前,当前活动的线程是   阻止,直到任务完成并且值可用。多数情况   在某些情况下,您应该使用等待等待来代替   直接进入酒店。“

此方案的最佳解决方案是删除await&amp; amp;来自方法&amp;的async 仅使用任务返回结果。它不会弄乱你的执行顺序。

答案 4 :(得分:2)

如果在调用服务/ API异步函数后没有回调或控件挂起。

您必须配置Context以在同一个被调用的上下文中返回结果。 使用TestAsync().ConfigureAwait(continueOnCapturedContext: false);

您将仅在Web应用程序中遇到此问题,但不会在Static void main

中遇到此问题

答案 5 :(得分:0)

@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