There are actually three variants.
var task = Task.Run(() => DoSomethingExpensiveAsync());
^ This one declares a new anonymous non-async function that calls DoSomethingExpensiveAsync()
and returns its Task
. The compiler compiles this anonymous function and passes it as an argument to Task.Run()
.
var task = Task.Run( async () => await DoSomethingExpensiveAsync() );
^ This one declares a new anonymous async function that calls DoSomethingExpensiveAsync()
. It then returns an incomplete Task
, waits for DoSomethingExpensiveAsync()
to finish, and then signals the task as complete.
var task = Task.Run(DoSomethingExpensiveAsync);
^ This one does not declare a new anonymous function at all. A direct reference to DoSomethingExpensiveAsync
will be passed as an argument to Task.Run()
.
All of these are valid because all three versions return a Task
and therefore match the overload of Task.Run()
that accepts a Func<Task>
.
As a black box, all three calls will end up doing the same thing. However the first two result in a new function being compiled (although I'm not certain it wouldn't be optimized away) and the second one also results in another state machine being created for it.
The difference might be clearer if we rewrite them without using lambda expressions or anonymous functions. The following code does exactly the same thing:
//This is the same as Task.Run( () => DoSomethingExpensiveAsync());
Task Foo()
{
return DoSomethingExpensiveAsync();
}
var task = Task.Run(Foo);
//This is the same as Task.Run(async () => await DoSomethingExpensiveAsync());
async Task Bar()
{
return await DoSomethingExpensiveAsync();
}
var task = Task.Run(Bar);
The difference between these two is that one "elides" tasks while the other doesn't. Stephen Cleary has written a whole blog on the subject.