ChatGPT解决这个技术问题 Extra ChatGPT

'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:

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?

You should avoid returning void from async methods. It's only for backward compatibility with exisiting event handlers, mostly in interface code. If your async method does not return anything, it should return Task. I had numerous problems with MSTest and void returning async tests.
@ghord: MSTest doesn't support async void unit test methods at all; they simply won't work. However, NUnit does. That said, I agree with the general principle of preferring async Task over async void.
@StephenCleary Yes, though it was allowed in betas of VS2012, which was causing all kinds of issues.

H
Herman Schoenfeld

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.


-1 for encouraging the use of async void unit test methods and removing same-thread guarantees provided by the SynchronizationContext from the system under test.
@StephenCleary: there's no "enouraging" of async void. It's merely employing valid c# construct to solve the deadlock issue. The above snippet is an indispensible and simple work-around to the OP's issue. Stackoverflow is about solutions to problems, not verbose self-promotion.
@StephenCleary: Your articles don't really articulate the solution (at least not clearly) and even if you had a solution, you'd use such constructs indirectly. My solution doesn't explicitly use contexts, so what? The point is, mine works and it's a one-liner. Didn't need two blog posts and thousands of words to solve the issue. NOTE: I don't even use async void, so I don't really know what you're on about.. Do you see "async void" anywhere in my concise and proper answer?
@HermanSchoenfeld, if you added the why to the how, I believe your answer would benefit a lot.
I know this is kind of late, but you should be using .GetAwaiter().GetResult() instead of .Result so that any Exception is not wrapped.
S
Stephen Cleary

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.

In this case, your SynchronizationContext is the one used by NUnit to execute async void test methods. I would try using async Task test methods instead.


changing to async Task worked, now I need to read the contents of your links a couple of times, ty sir.
@StephenCleary what if I have to call an async method inside a constructor? Constructors can't be async.
@StephenCleary In nearly all of your replies on SO and in your articles, all I ever see you talk about is replacing Wait() with making the calling method async. But to me, this seems to be pushing the problem upstream. At some point, something has to be managed synchronously. What if my function is purposefully synchronous because it manages long running worker threads with Task.Run()? How do I wait on that to finish without deadlocking inside my NUnit test?
@void.pointer: At some point, something has to be managed synchronously. - not at all. For UI apps, the entrypoint can be an async void event handler. For server apps, the entrypoint can be an async Task<T> action. It is preferable to use async for both to avoid blocking threads. You can have your NUnit test be synchronous or async; if async, make it async Task instead of async void. If it's synchronous, it shouldn't have a SynchronizationContext so there shouldn't be a deadlock.
I came into this question from trying to resolve a deadlock (Task stuck in Scheduled status) and I found that making the event handler async did not help. I had to wrap as per Task.Run(() => asyncGetValue()).Result
V
Vladimir

You can avoid deadlock adding ConfigureAwait(false) to this line:

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


a
abatishchev

You are blocking the UI by using Task.Result property. In MSDN Documentation they have clearly mentioned that,

"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."

The best solution for this scenario would be to remove both await & async from methods & use only Task where you're returning result. It won't mess your execution sequence.


O
Ogglas

An addition to the answer given by @HermanSchoenfeld. Unfortunately the quote below is not true:

No deadlock issues will occur due to the use of 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. 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.

Source:

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


P
Peter Mortensen

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.

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

You will be facing this issue only in web applications, but not in static void main.


ConfigureAwait avoids deadlocking in certain scenarios by not running in the original thread context.

关注公众号,不定期副业成功案例分享
Follow WeChat

Success story sharing

Want to stay one step ahead of the latest teleworks?

Subscribe Now