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 await
s 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 Task
doesn'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
- Using an
async
delegate insideTask.Run
means that you actually run aTask<Task>
. This is hidden from you by the fact thatTask.Run
is async aware and unwraps the inner task for you, something thatTask.Factory.StartNew
didn't do When you use an async delegate with
Task.Run
, you create a new thread, then yield control once you hitawait 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 bothTask.Run(Func<Task>)
andTask.Run(Action)
, what type is the async anonymous function compiled to? Anasync void
or aFunc<Task>
? My gut feeling says it will compile down to anasync void
purely because its a non-generic type however the C# Compiler might be smart and giveFunc<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
An Efficient Way to Base64 Encode a Byte Array
How to Call a SQL Stored Procedure Using Entityframework 7 and ASP.NET 5
Direct Method from SQL Command Text to Dataset
How to Upload Files Using Ajax to ASP.NET MVC Controller Action
Accuracy of Textrenderer.Measuretext Results
Any Trick to Use Opacity on a Panel in Visual Studio Window Form
Ef4 Cause Circular Reference in Web Service
Visualizing an Ast Created with Antlr (In a .Net Environment)
How Does Stringbuilder Work Internally in C#
Convert Object to JSON String in C#
Draw Multiple Freehand Polyline or Curve Drawing - Adding Undo Feature
Fastest Way to Remove White Spaces in String
Getting Relative Virtual Path from Physical Path
Split String into Smaller Strings by Length Variable
Failed to Update .Mdf Database Because the Database Is Read-Only (Windows Application)