C# ';等待&x27;工作正常,但调用task.Result会挂起/死锁

C# ';等待&x27;工作正常,但调用task.Result会挂起/死锁,c#,nunit,task,deadlock,async-await,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 CheckStatusTw

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

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

为什么上一个测试挂起?

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


在这种情况下,NUnit使用
SynchronizationContext
执行
async void
测试方法。我将尝试使用
异步任务
测试方法。

您可以避免死锁,将
配置等待(false)
添加到此行:

IRestResponse<DummyServiceStatus> response = await restResponse;
IRestResponse response=wait response;
=>

IRestResponse response=wait response.configurewait(false);

我在我的博客文章中描述了这个陷阱,通过异步方法获取值:

var result = Task.Run(() => asyncGetValue()).Result;
同步调用异步方法

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

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

您正在使用Task.Result属性阻止UI。 他们在报告中明确提到,

“Result属性是一个阻塞属性。如果您试图访问它 在其任务完成之前,当前处于活动状态的线程 在任务完成且值可用之前被阻止。在大多数情况下 在这种情况下,应使用AwaitAwait而不是 直接访问该属性。”


此场景的最佳解决方案是从方法中同时删除wait和async,只在返回结果的地方使用Task。它不会打乱您的执行顺序。

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

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


您将只在web应用程序中遇到此问题,而不是在@hermanschoonfeld给出的答案的附加部分中遇到此问题。不幸的是,下面的引语不正确:

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

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

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

资料来源:


您应该避免从异步方法返回void。它仅用于与现有事件处理程序(主要是在接口代码中)的向后兼容性。如果您的异步方法不返回任何内容,它应该返回任务。我在MSTest和void返回异步测试时遇到了很多问题。@ghord:MSTest根本不支持
异步void
单元测试方法;它们根本不起作用。然而,努尼特确实如此。也就是说,我同意将
异步任务
置于
异步无效
@StephenCleary之上的一般原则,是的,尽管它在VS2012的betas中是允许的,这导致了各种各样的问题。改为异步任务有效,现在我需要多次阅读您链接的内容,ty先生。@MarioLopez:解决方案是一直使用“
async
”(正如我在MSDN文章中提到的)。换句话说,正如我博客文章的标题所说,“不要阻塞异步代码”。@StephenCleary如果我必须在构造函数中调用异步方法怎么办?构造函数不能是异步的。@StephenCleary在您对SO和您的文章的几乎所有回复中,我所看到的您谈论的都是将
Wait()
替换为使调用方法
async
。但对我来说,这似乎将问题推向了上游。在某种程度上,有些事情必须同步管理。如果我的函数是有目的地同步的,因为它使用
Task.Run()
管理长时间运行的工作线程,该怎么办?我如何在NUnit测试中等待它完成而不出现死锁?@void.pointer:
在某个点上,某些东西必须同步管理。
-一点也不。对于UI应用程序,入口点可以是
异步void
事件处理程序。对于服务器应用程序,入口点可以是
异步任务
操作。最好同时使用
async
,以避免阻塞线程。您可以让NUnit测试同步或异步;如果是异步的,则将其设置为
async Task
,而不是
async void
。如果它是同步的,它不应该有一个
SynchronizationContext
,所以不应该有死锁。-1用于鼓励使用
async void
单元测试方法,并从被测系统中删除由
SynchronizationContext
提供的相同线程保证。@StephenCleary:没有“enouring”异步void。它只是使用有效的c#构造来解决死锁问题。上面的片段是围绕OP问题的一个不可或缺的简单工作。Stackoverflow是关于问题的解决方案,而不是冗长的自我推销。@StephenCleary:你的文章并没有真正阐明解决方案(至少不清楚),即使你有一个解决方案,你也会间接地使用这些结构。我的解决方案没有显式使用上下文,那又怎样?重点是,我的工作,它是一个单一的班轮。解决这个问题不需要两篇博文和数千字。注意:我甚至不使用async void,所以我真的不知道你在说什么。。在我的简明版和专业版中,你看到“async void”了吗
IRestResponse<DummyServiceStatus> response = await restResponse.ConfigureAwait(false);
var result = Task.Run(() => asyncGetValue()).Result;
Task.Run( () => asyncMethod()).Wait();
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; 
}