Waiting for Async/Await Inside a Task

Waiting for async/await inside a task

It's discouraged to use Task.Factory.StartNew with async-await, you should be using Task.Run instead:

var t = Task.Run(
async () =>
{
Foo.Fim();
await Foo.DoBar();
});

The Task.Factory.StartNew api was built before the Task-based Asynchronous Pattern (TAP) and async-await. It will return Task<Task> because you are starting a task with a lambda expression which happens to be async and so returns a task. Unwrap will extract the inner task, but Task.Run will implicitly do that for you.


For a deeper comparison, there's always a relevant Stephen Toub article: Task.Run vs Task.Factory.StartNew

Async/Await action within Task.Run()

Is there any benefit of having async/await within the Task.Run()

An async method returns to the caller as soon as the first await is hit (that operates on a non-completed task). So if that first execution "streak" of an async method takes a long time Task.Run will alter behavior: It will cause the method to immediately return and execute that first "streak" on the thread-pool.

This is useful in UI scenarios because that way you can make 100% sure that you are not blocking the UI. Example: HttpWebRequestdoes DNS resolution synchronously even when you use one of the async methods (this is basically a library bug/design error). This can pause the UI thread. So you can use Task.Run to be 100% sure that the UI is never blocked for longer than a few microseconds.

So back to the original question: Why await inside a Task.Run body? For the same reason you normally await: To unblock the thread.

Task.Wait doesn't wait for async method completion

I still don't quite understand why Task doesn't work correctly with async/await inside delegate.

Because the Task constructor is only used for creating Delegate Tasks - i.e., tasks that represent synchronous code to be run. Since the code is synchronous, your async lambda is being treated as an async void lambda, which means the Task instance won't asynchronously wait for AsyncTest to complete.

More to the point, the Task constructor should never, ever be used in any code, anywhere, for any reason. It has literally zero valid use cases.

A good replacement for Task.Task is Task.Run, which does understand async lambdas.

In my real program Task has deferred execution. Task object is created in one place, and then later after specific condition executed by another part of program.

In that case, use an asynchronous delegate. Specifically, Func<Task>.

static async Task Main(string[] args)
{
Func<Task> func = AsyncTest;

// Later, when we're ready to run.
await func();
Console.WriteLine("Main finished");
}

private static async Task AsyncTest()
{
Thread.Sleep(2000);
await Task.Delay(2000);
Console.WriteLine("Method finished");
}

Understanding the use of Task.Run + Wait() + async + await used in one line

You can break this apart into several parts:

async () => { await SomeClass.Initiate(new Configuration()); }

Is a lambda expression that defines an async method that just awaits another method. This lambda is then passed to Task.Run:

Task.Run(async () => { await SomeClass.Initiate(new Configuration()); })

Task.Run executes its code on a thread pool thread. So, that async lambda will be run on a thread pool thread. Task.Run returns a Task which represents the execution of the async lambda. After calling Task.Run, the code calls Task.Wait:

Task.Run(async () => { await SomeClass.Initiate(new Configuration()); }).Wait();

This will block the main console app until the async lambda is completely finished.

If you want to see how it's broken out further, the following is roughly equivalent:

static async Task AnonymousMethodAsync()
{
await SomeClass.Initiate(new Configuration());
}

static void Main(string[] args)
{
var task = Task.Run(() => AnonymousMethodAsync());
task.Wait();
while (true) ;
}

Why Task stops executing on call any async method if I create it with new Task(action) - .Start() instead of Task.Run()?

The answer is discussed here. Essentially the Task construction takes an Action<> and not a Func<> and so the async delegate is not really being treated as such.

Trying to Understand Task.Run + async + Wait()/Result

What is the difference between the two?

Task.Run starts running the delegate on a thread pool thread; invoking the method directly starts running the delegate on the current thread.

When learning async, it's helpful to separate out everything so you can see exactly what's going on:

entities = DoAnotherWork(entities).Result;

is equivalent to:

var entitiesTask = DoAnotherWork(entities);
entities = entitiesTask.Result;

and this code:

Task.Run(async () => entities = await DoAnotherWork(entities)).Wait();

is equivalent to:

async Task LambdaAsMethod()
{
entities = await DoAnotherWork(entities);
}
var runTask = Task.Run(LambdaAsMethod);
runTask.Wait();

Which code is appropriate and/or safe (= won't cause a deadlock)?

You should avoid Task.Run in an ASP.NET environment because it will interfere with the ASP.NET handling of the thread pool and force a thread switch when none is necessary.

In what situation is a deadlock caused if it is?

The common deadlock scenario requires two things:

  1. Code that blocks on asynchronous code instead of properly using await.
  2. A context that enforces synchronization (i.e., only allows one block of code "in" the context at a time).

The best solution is to remove the first condition; in other words, use "async all the way". To apply that here, the best resolution is to remove the blocking completely:

public Task async ProcessAsync(IEnumerable<EventData> messages)
{
...
var entities = await ConvertToEntityAsync(messages);
...
}

public async Task<List<Entity>> ConvertToEntityAsync(IEnumerable<EventData> messages)
{
var serializedMessages = Serialize(messages);
var entities = autoMapper.Map<Entity[]>(serializedMessages);

entities = await DoAnotherWork(entities);

return entities;
}

Why Task.Run() resolve deadlock I had? (see below for detail)

.NET Core does not have a "context" at all, so it uses the thread pool context. Since .NET Core doesn't have a context, it removes the second condition for the deadlock, and the deadlock will not occur. If you're running this in an ASP.NET Core project.

My team had deadlock issues a few times when we used AbcAsync().Result or .Wait() (the method was called in a NET Core Web API methods and deadlocks occurred mostly when we ran unit tests that execute the method)

Some unit test frameworks do provide a context - most notably xUnit. The context provided by xUnit is a synchronizing context, so it acts more like a UI context or ASP.NET pre-Core context. So when your code is running in a unit test, it does have the second condition for the deadlock, and the deadlock can happen.

As noted above, the best solution is to remove the blocking completely; this will have the nice side effect of making your server more efficient. But if the blocking must be done, then you should wrap your unit test code in a Task.Run, not your ASP.NET Core code.



Related Topics



Leave a reply



Submit