'await'有效,但调用task.Result挂起/死锁


126

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

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

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


7
您应该避免从异步方法返回void。它仅是为了与现有事件处理程序(主要是接口代码)向后兼容。如果您的异步方法未返回任何内容,则应返回Task。我在MSTest上遇到了很多问题,并且返回异步测试无效。
ghord 2013年

2
@ghord:MSTest根本不支持async void单元测试方法。他们根本行不通。但是,NUnit可以。就是说,我同意优先async Task于的一般原则async void
Stephen Cleary 2013年

@StephenCleary是的,尽管它在VS2012的beta中被允许,这引起了各种各样的问题。
ghord 2013年

Answers:


88

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

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


4
更改为异步Task工作正常,现在我需要阅读您的链接的内容几次,ty先生。
Johan Larsson 2013年

@MarioLopez:解决方案是“ async一路使用”(如我的MSDN文章所述)。换句话说,正如我博客文章的标题所述,“不要阻止异步代码”。
斯蒂芬·克莱里

1
@StephenCleary如果必须在构造函数中调用异步方法怎么办?构造函数不能异步。
Raikol Amaro

1
@StephenCleary在您对SO和文章的几乎所有回复中,我所看到的仅是将其替换Wait()为calling方法async。但是对我来说,这似乎将问题推向了上游。在某些时候,一些必须要同步管理。如果我的函数由于使用来管理长时间运行的工作线程而故意同步,该Task.Run()怎么办?如何等待NUnit测试完成而不会死锁?
void.pointer

1
@ void.pointer:At some point, something has to be managed synchronously.-完全没有。对于UI应用程序,入口点可以是async void事件处理程序。对于服务器应用程序,入口点可以是一个async Task<T>动作。async为避免阻塞线程,最好同时使用两者。您可以让NUnit测试是同步或异步的。如果异步,则使其async Task代替async void。如果它是同步的,则不应有同步,因此不应有SynchronizationContext死锁。
斯蒂芬·克莱里

222

通过异步方法获取值:

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

同步调用异步方法

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

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


15
-1,用于鼓励使用async void单元测试方法并SynchronizationContext从被测系统中删除所提供的相同线程保证。
史蒂芬·克雷里

68
@StephenCleary:没有“鼓励”异步无效。它只是使用有效的c#构造来解决死锁问题。上面的代码段是OP问题不可或缺的简单解决方法。Stackoverflow是关于解决问题的方法,而不是冗长的自我宣传。
Herman Schoenfeld,2015年

81
@StephenCleary:您的文章并没有真正阐明解决方案(至少不清楚),即使您有解决方案,也可以间接使用此类构造。我的解决方案未明确使用上下文,那又如何呢?关键是,我的工作是单线的。不需要两个博客文章和数千个单词即可解决问题。注意:我什至不使用async void,所以我真的不知道你在干什么。
Herman Schoenfeld,2015年

15
@HermanSchoenfeld,如果添加了为什么怎么样,我相信你的答案会受益颇多。
ironstone13

19
我知道这有点晚了,但是您应该使用.GetAwaiter().GetResult()而不是,.Result这样任何东西Exception都不会被包装。
卡米洛·特雷文托

15

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

IRestResponse<DummyServiceStatus> response = await restResponse;

=>

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

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


9

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

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

对于这种情况,最好的解决方案是从方法中删除await和async并仅在要返回结果的Task上使用。它不会弄乱您的执行顺序。


3

如果没有任何回调或控件挂断,则在调用service / API异步函数后,必须将Context配置为在相同的调用上下文上返回结果。

TestAsync().ConfigureAwait(continueOnCapturedContext: false);

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


ConfigureAwait通过不在原始线程上下文中运行避免在某些情况下出现死锁。
davidcarr '19
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.