Is Using an an 'Async' Lambda with 'Task.Run()' Redundant

is using an an `async` lambda with `Task.Run()` redundant?

Normally, the intended usage for Task.Run is to execute CPU-bound code on a non-UI thread. As such, it would be quite rare for it to be used with an async delegate, but it is possible (e.g., for code that has both asynchronous and CPU-bound portions).

However, that's the intended usage. I think in your example:

var task = Task.Run(async () => { await Foo.StartAsync(); });
task.Wait();

It's far more likely that the original author is attempting to synchronously block on asynchronous code, and is (ab)using Task.Run to avoid deadlocks common in that situation (as I describe on my blog).

In essence, it looks like the "thread pool hack" that I describe in my article on brownfield asynchronous code.

The best solution is to not use Task.Run or Wait:

await Foo.StartAsync();

This will cause async to grow through your code base, which is the best approach, but may cause an unacceptable amount of work for your developers right now. This is presumably why your predecessor used Task.Run(..).Wait().

Correctly awaiting Task.Run with async lambda expression

Your code will wait till the end of the compute using the task mechanism. This will not block your thread but it will not execute code beyond this point until everything in your loop is done.

public static async Task Test()
{
await Task.Run(async () =>
{
for (int i = 0; i < 10; i++)
{
await Task.Delay(TimeSpan.FromSeconds(1));
Console.WriteLine($"inside {i}");
}
});
Console.WriteLine("Done");
}

will give the following output:

inside 0
inside 1
inside 2
inside 3
inside 4
inside 5
inside 6
inside 7
inside 8
inside 9
Done

Are the await / async keywords needed under Task.Run?

How come the compiler let me do this and what is happening behind the scene?

The overload of Task.Run that you're invoking takes a Func<Task> - that is, a Task-returning function. It doesn't matter where the Task comes from; the function just needs to return it from somewhere.

If you pass a delegate without async and await, then the delegate is just calling a Task-returning function and returns that same Task. If you pass a delegate with async and await, then the delegate calls the Task-returning function and awaits it; the actual Task returned from the delegate is created by the async keyword.

In this case, the two are semantically equivalent. Using the async/await keywords are a bit less efficient, since the compiler creates a state machine for the async delegate.

Is there a situation where adding them will make a difference?

Yes. In the general case, you should keep async and await. Only remove them in extremely simple "passthrough" situations like the one here.

Is there any reason to run async code inside a Task.Run?

The fact that a method returns a Taskdoesn't mean it yields back immediately. It might have have some time/CPU consuming setup before an I/O operation, for example.

For that reason, it is usual to see, on client UIs, everything outside of the UI being called inside Task.Run.

That being said, this is not such a case:

public async Task SaveStuff()
{
await Task.Run(() => SaveStuffAsync().ConfigureAwait(false));
await Task.Run(() => SendToExternalApiAsync().ConfigureAwait(false));
}

That causes one extra execution in the UI thread only to schedule work on the thread pool.

this would be more acceptable:

public async Task SaveStuff()
{
await Task.Run(
async () =>
{
await SaveStuffAsync();
await SendToExternalApiAsync();
});
}

There's no need to invoke ConfigureAwait(false) because it's guaranteed to not have a SynchronizationContext.

The difference between this and your last snippet is where and how that code is being invoked,

The 'await' operator can only be used with an async lambda expression

int processCount = await Task.Run<int>(() =>

Should be

int processCount = await Task.Run<int>(async () =>

Remember that a lambda is just shorthand for defining a method. So, your outer method is async, but in this case you're trying to use await within a lambda (which is a different method than your outer method). So your lambda must be marked async as well.

What difference does it make - running an 'async' action delegate with a Task.Run (vs default action delegate)?

My question is : how does this make any difference to this method (or
it does not) ?

A couple of differences

  1. Using an async delegate inside Task.Run means that you actually run a Task<Task>. This is hidden from you by the fact that Task.Run is async aware and unwraps the inner task for you, something that Task.Factory.StartNew didn't do
  2. When you use an async delegate with Task.Run, you create a new thread, then yield control once you hit await Task.Delay. The continuation will run on an arbitrary thread-pool thread. Additionaly, the delegate is transformed into a state-machine by the compiler.

    With the normal delegate, you create a thread, synchronously block it for 5 seconds, and then continue at the point you left off. No state-machines, no yielding.


So, even if it yields with an await on Task.Delay, what is the use in this scenario, since the thread is anyways a isolated thread
not used for anything else and even if it just uses Thread.Sleep, the
thread would still context switch to yield to other threads for the
processor.

The use of async with Task.Run can be when you want to do both CPU and IO bound work, all in a dedicated thread. You're right in thinking that after the async delegate yields, it returns on an arbitrary thread. Though, if you hadn't used Task.Run, and the async method executed from a thread that had a custom synchronization context attached (such as WinformsSynchronizationContext), any work after the await would yield back to the UI message loop, unless you used ConfigureAwait(false).

To tell the truth, I haven't seen that many scenarios where Task.Run and async are used correctly. But it does make sense at times.

Explicitly use a FuncTask for asynchronous lambda function when Action overload is available

Because Task.Run has signatures of both Task.Run(Func<Task>) and Task.Run(Action), what type is the async anonymous function compiled to? An async void or a Func<Task>? My gut feeling says it will compile down to an async void purely because its a non-generic type however the C# Compiler might be smart and give Func<Task> types preference.

The general rule, even without async, is that a delegate with a return type is a better match than a delegate without a return type. Another example of this is:

static void Foo(Action a) { }
static void Foo(Func<int> f) { }
static void Bar()
{
Foo(() => { throw new Exception(); });
}

This is unambiguous and calls the second overload of Foo.

Also, is there a way to explicitly declare which overload I wish to use?

A nice way to make this clear is to specify the parameter name. The parameter names for the Action and Func<Task> overloads are different.

Task.Run(action: async () => {
await Task.Delay(1000);
});
Task.Run(function: async () => {
await Task.Delay(1000);
});


Related Topics



Leave a reply



Submit