Reputation: 33
var L1 =
Task.Run(() =>
{
return Task.Run(() =>
{
return Task.Run(() =>
{
return Task.Run(() =>
{
return new Dummy();
});
});
});
});
var L2 =
Task.Run(async () =>
{
return await Task.Run(async () =>
{
return await Task.Run(async () =>
{
return await Task.Run(async () =>
{
return new Dummy();
});
});
});
});
var L3 =
Task.Run(async () =>
{
return Task.Run(async () =>
{
return Task.Run(async () =>
{
return Task.Run(async () =>
{
return new Dummy();
});
});
});
});
var r1 = L1.Result;
var r2 = L2.Result;
var r3 = L3.Result;
======================================================================
By first glance, L1, L2 and L3 all looks like
Task<Task<Task<Task<Dummy>>>>
Turns out, L1 and L2 are simply Task<Dummy>
So, I look up MSDN for Task.Run
(my overloaded Task.Run is: Task.Run<TResult>(Func<Task<TResult>>)
)
MSDN says:
Return Value is: A Task(TResult) that represents a proxy for the Task(TResult) returned by function.
It also says in the Remarks:
The
Run<TResult>(Func<Task<TResult>>)
method is used by language compilers to support the async and await keywords. It is not intended to be called directly from user code.
So, it looks like I should not use this overloaded Task.Run in my code,
and if I do, compiler will strip off the extra layers of Task<Task<Task<...<TResult>>>>
and simply give you a Task<TResult>
If you mouse over L1 and L2, it tells you it is Task<Dummy>
, not my
expected Task<Task<Task<Task<Dummy>>>>
So far so good, until I look at L3
L3 looks almost exactly the same as L1 and L2. The difference is:
L3 only has async
keyword, not the await
, unlike L1 and L2, where L1 has neither of them and L2 has both of them
Surprisingly, L3 is now considered as Task<Task<Task<Task<Dummy>>>>
, unlike L1 and L2, where both are considered Task<Dummy>
My Questions:
1.
What causes the Compiler to treat L3 differently from L1 and L2. Why simply adding 'async'
to L1 (or removing await
from L2) causes the compiler to treat it differently ?
2.
If you cascade more Task.Run to L1/L2/L3, Visual Studio crashes. I am using VS2013, if I cascade 5 or more layers of Task.Run, it crashes. 4 layers is the best I can get, that's why I use 4 layers as my example. Is it just me ?
What happen to the compiler in translating Task.Run ?
Thanks
Upvotes: 3
Views: 340
Reputation: 456527
What causes the Compiler to treat L3 differently from L1 and L2. Why simply adding 'async' to L1 (or removing await from L2) causes the compiler to treat it differently ?
Because async
and await
change the types in the lambda expressions. You can think of async
as "adding" a Task<>
wrapper, and await
as "removing" a Task<>
wrapper.
Just consider the types involved in the innermost calls. First, L1:
return Task.Run(() =>
{
return new Dummy();
});
The type of () => { return new Dummy(); }
is Func<Dummy>
, and so the return type of that overload of Task.Run
is Task<Dummy>
.
So the type of () => ###Task<Dummy>###
is Func<Task<Dummy>>
, which calls a different overload of Task.Run
, with a return type of Task<Dummy>
. And so on.
Now consider L2:
return await Task.Run(async () =>
{
return new Dummy();
});
The type of async () => { return new Dummy(); }
is Func<Task<Dummy>>
, so the return type of that overload of Task.Run
is Task<Dummy>
.
The type of async () => await ###Task<Dummy>###
is Func<Task<Dummy>>
, so it calls the same overload of Task.Run
with a result type of Task<Dummy>
. And so on.
Finally, L3:
return Task.Run(async () =>
{
return new Dummy();
});
The type of async () => { return new Dummy(); }
is again Func<Task<Dummy>>
, so the return type of that overload of Task.Run
is Task<Dummy>
.
The type of async () => { return ###Task<Dummy>### }
is Func<Task<Task<Dummy>>>
. Note the nested task. So, the same overload of Task.Run
is called again, but its return type is Task<Task<Dummy>>
this time.
Now, you just repeat for each level. The type of async () => { return ###Task<Task<Dummy>>### }
is Func<Task<Task<Task<Dummy>>>>
. The same overload of Task.Run
is called again, but its return type is Task<Task<Task<Dummy>>>
this time. And so on.
If you cascade more Task.Run to L1/L2/L3, Visual Studio crashes. I am using VS2013, if I cascade 5 or more layers of Task.Run, it crashes. 4 layers is the best I can get, that's why I use 4 layers as my example. Is it just me ? What happen to the compiler in translating Task.Run ?
Who cares? No real-world code would ever do this. There are well-known scenarios that are extremely difficult for the compiler to handle in a reasonable time frame; using lambda expressions in method overload resolution is one. Nested calls using lambda expressions makes the compiler work exponentially harder.
Upvotes: 6