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

Joh*_*son 118 c# nunit deadlock task 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;
}
Run Code Online (Sandbox Code Playgroud)

我将此扩展方法用于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;
    }
}
Run Code Online (Sandbox Code Playgroud)
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; }
}
Run Code Online (Sandbox Code Playgroud)

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

Her*_*eld 207

通过异步方法获取值:

var result = Task.Run(() => asyncGetValue()).Result;
Run Code Online (Sandbox Code Playgroud)

同步调用异步方法

Task.Run( () => asyncMethod()).Wait();
Run Code Online (Sandbox Code Playgroud)

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

  • @StephenCleary:你的文章并没有真正阐明解决方案(至少不清楚),即使你有一个解决方案,你也会间接使用这些结构.我的解决方案没有明确使用上下文,那又怎样?关键是,我的工作,它是一个单行.不需要两篇博文和数千个单词来解决问题.注意:我甚至不使用*async void*,所以我真的不知道你在做什么..你在我简明扼要的答案中看到"async void"吗? (76认同)
  • @StephenCleary:async void没有"令人沮丧".它只是使用有效的c#构造来解决死锁问题.以上片段是OP问题不可或缺的简单解决方案.Stackoverflow是解决问题的方法,而不是冗长的自我推销. (61认同)
  • 我知道这有点晚了,但你应该使用`.GetAwaiter().GetResult()`而不是`.Result`这样任何`Exception`都不会被包装. (17认同)
  • -1,用于鼓励使用`async void`单元测试方法,并从被测系统中删除`SynchronizationContext`提供的相同线程保证. (14认同)
  • @HermanSchoenfeld,如果你将*why*添加到*how*,我相信你的答案会受益匪浅. (13认同)
  • 当你只需要`sync`调用`async`方法时,这是正确而简单的方法.例如,所有`refit`,`restease`和其他api生成器都只提供异步方法.有时候你只想打电话给该死的东西而不是整个班级的同步. (5认同)
  • 它显然不是"不可或缺的",因为我的解决方案在没有"async void","Task.Run","Result"或"Wait"的情况下工作. (3认同)
  • 你能解释一下它的工作原理吗?这个解决方案与仅使用`Result`属性有什么区别? (3认同)
  • Vorsprung durch technik.你必须喜欢日耳曼的效率. (2认同)

Ste*_*ary 82

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

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

  • 更改为异步任务工作,现在我需要阅读链接的内容几次,先生. (4认同)
  • @StephenCleary 如果我必须在构造函数内调用异步方法怎么办?构造函数不能是异步的。 (2认同)
  • @void.pointer:“在某些时候,某些事情必须同步管理。” - 根本不需要。对于 UI 应用程序,入口点可以是“async void”事件处理程序。对于服务器应用程序,入口点可以是“async Task&lt;T&gt;”操作。最好对两者使用“async”以避免阻塞线程。您可以让 NUnit 测试同步或异步;如果是异步的,则将其设置为“async Task”而不是“async void”。如果它是同步的,它不应该有“SynchronizationContext”,因此不应该出现死锁。 (2认同)
  • 我在尝试解决死锁时遇到了这个问题(任务陷入​​“已计划”状态),我发现使事件处理程序为“异步”并没有帮助。我必须按照 `Task.Run(() =&gt; asyncGetValue()).Result` 进行包装 (2认同)

Vla*_*mir 16

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

IRestResponse<DummyServiceStatus> response = await restResponse;
Run Code Online (Sandbox Code Playgroud)

=>

IRestResponse<DummyServiceStatus> response = await restResponse.ConfigureAwait(false);
Run Code Online (Sandbox Code Playgroud)

我在我的博客文章"异步/等待的陷阱"中描述了这个陷阱


Dar*_*ght 8

您正在使用Task.Result属性阻止UI.在MSDN文档中,他们清楚地提到过,

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

此方案的最佳解决方案是从方法中删除await和async,并仅使用返回结果的任务.它不会弄乱你的执行顺序.