简体   繁体   English

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

[英]'await' works, but calling task.Result hangs/deadlocks

I have the following four tests and the last one hangs when I run it.我有以下四个测试,最后一个在我运行时挂起。 Why does this happen:为什么会发生这种情况:

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

I use this extension method for restsharp RestClient :我将此扩展方法用于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; }
}

Why does the last test hang?为什么最后一个测试挂起?

Acquiring a value via an async method:通过异步方法获取值:

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

Syncronously calling an async method同步调用异步方法

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

No deadlock issues will occur due to the use of Task.Run.不会因为使用Task.Run而出现死锁问题。

You're running into the standard deadlock situation that I describe on my blog and in an MSDN article : the async method is attempting to schedule its continuation onto a thread that is being blocked by the call to Result .您遇到了我在我的博客MSDN 文章中描述的标准死锁情况: async方法试图将其继续安排到被调用Result阻塞的线程上。

In this case, your SynchronizationContext is the one used by NUnit to execute async void test methods.在这种情况下,您的SynchronizationContext是 NUnit 用来执行async void测试方法的。 I would try using async Task test methods instead.我会尝试改用async Task测试方法。

You can avoid deadlock adding ConfigureAwait(false) to this line:您可以避免死锁将ConfigureAwait(false)添加到此行:

IRestResponse<DummyServiceStatus> response = await restResponse;

=> =>

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

I've described this pitfall in my blog post Pitfalls of async/await我在我的博客文章异步/等待的陷阱中描述了这个陷阱

You are blocking the UI by using Task.Result property.您正在使用 Task.Result 属性阻止 UI。 In MSDN Documentation they have clearly mentioned that,MSDN 文档中,他们明确提到,

"The Result property is a blocking property. If you try to access it before its task is finished, the thread that's currently active is blocked until the task completes and the value is available. In most cases, you should access the value by using Await or await instead of accessing the property directly." " Result属性是一个阻塞属性。如果你在它的任务完成之前尝试访问它,当前处于活动状态的线程将被阻塞,直到任务完成并且值可用。在大多数情况下,你应该使用Await访问该值或等待而不是直接访问该属性。”

The best solution for this scenario would be to remove both await & async from methods & use only Task where you're returning result.这种情况的最佳解决方案是从方法中删除 await 和 async ,并仅在返回结果的地方使用Task It won't mess your execution sequence.它不会弄乱你的执行顺序。

If you don't get any callbacks or the control hangs up, after calling the service/API async function, you have to configure Context to return a result on the same called context.如果你没有得到任何回调或者控件挂了,在调用服务/API 异步函数后,你必须配置 Context 在同一个被调用的上下文上返回结果。

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

You will be facing this issue only in web applications, but not in static void main .您只会在 Web 应用程序中遇到此问题,而不会在static void main遇到此问题。

An addition to the answer given by @HermanSchoenfeld. @HermanSchoenfeld 给出的答案的补充。 Unfortunately the quote below is not true:不幸的是,下面的引用是不正确的:

No deadlock issues will occur due to the use of Task.Run.不会因为使用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; 
}

The execution is wrapped inside a Task.Run, this will schedule the task on the threadpool the block the calling thread.执行被包装在一个 Task.Run 中,这将在线程池上调度任务并阻塞调用线程。 This is okay, as long as the calling thread is not a threadpool thread.这没问题,只要调用线程不是线程池线程。 If the calling thread is from the threadpool then the following disaster happens: A new task is queued to the end of the queue, and the threadpool thread which would eventually execute the Task is blocked until the Task is executed.如果调用线程来自线程池,则会发生以下灾难:一个新任务排队到队列的末尾,最终执行该任务的线程池线程被阻塞,直到执行该任务。

In library code there is no easy solution as you cannot assume under what context your code is called.在库代码中没有简单的解决方案,因为您无法假设您的代码在什么上下文中被调用。 The best solution is to only call async code from async code, blocking sync APIs from sync methods, don't mix them.最好的解决方案是只从异步代码调用异步代码,从同步方法中阻塞同步 API,不要混合它们。

Source:来源:

https://medium.com/rubrikkgroup/understanding-async-avoiding-deadlocks-e41f8f2c6f5d https://medium.com/rubrikkgroup/understanding-async-avoiding-deadlocks-e41f8f2c6f5d

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM