将 Task.Run 与 async / await 关键字混合时出现奇怪的编译器行为
Weird compiler behavior when mixing Task.Run with async / await keyword
考虑以下 C# 代码
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;
======================================== ==============================
乍一看,L1、L2、L3都很像
Task<Task<Task<Task<Dummy>>>>
事实证明,L1 和 L2 只是 Task<Dummy>
所以,我在 MSDN 上查找 Task.Run
(我的重载 Task.Run 是:Task.Run<TResult>(Func<Task<TResult>>)
)
MSDN 说:
Return Value is: A Task(TResult) that represents a proxy for the Task(TResult) returned by function.
备注里也写着:
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.
所以,看起来我不应该在我的代码中使用这个重载的 Task.Run,
如果我这样做了,编译器会去掉 Task<Task<Task<...<TResult>>>>
的额外层,然后简单地给你一个 Task<TResult>
如果您将鼠标悬停在 L1 和 L2 上,它会告诉您它是 Task<Dummy>
,而不是我
预期的 Task<Task<Task<Task<Dummy>>>>
到目前为止一切顺利,直到我看到 L3
L3 看起来几乎与 L1 和 L2 完全一样。区别在于:
L3 只有 async
关键字,没有 await
,不像 L1 和 L2,L1 两个都没有,L2 都有
令人惊讶的是,L3 现在被视为 Task<Task<Task<Task<Dummy>>>>
,不像 L1 和 L2,两者都被视为 Task<Dummy>
我的问题:
1。
是什么导致编译器将 L3 与 L1 和 L2 区别对待。为什么简单地将 'async'
添加到 L1(或从 L2 中删除 await
)会导致编译器以不同方式对待它?
2。
如果将更多 Task.Run 级联到 L1/L2/L3,Visual Studio 会崩溃。我正在使用 VS2013,如果我级联 5 层或更多层 Task.Run,它会崩溃。 4 层是我能得到的最好的,这就是我使用 4 层作为示例的原因。只有我吗 ?
编译器在翻译 Task.Run 时发生了什么?
谢谢
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 ?
因为 async
和 await
更改了 lambda 表达式中的类型。您可以将 async
视为 "adding" 一个 Task<>
包装器,将 await
视为 "removing" 一个 Task<>
包装器。
只考虑最内层调用涉及的类型。首先,L1:
return Task.Run(() =>
{
return new Dummy();
});
() => { return new Dummy(); }
的类型是Func<Dummy>
,所以that overload of Task.Run
的return类型是Task<Dummy>
。
所以() => ###Task<Dummy>###
的类型是Func<Task<Dummy>>
,调用了different overload of Task.Run
,return的类型是Task<Dummy>
。等等。
现在考虑 L2:
return await Task.Run(async () =>
{
return new Dummy();
});
async () => { return new Dummy(); }
的类型是Func<Task<Dummy>>
,所以that overload of Task.Run
的return类型是Task<Dummy>
。
async () => await ###Task<Dummy>###
的类型是Func<Task<Dummy>>
,所以调用same overload of Task.Run
的结果类型是Task<Dummy>
。等等。
最后,L3:
return Task.Run(async () =>
{
return new Dummy();
});
async () => { return new Dummy(); }
的类型又是Func<Task<Dummy>>
,所以that overload of Task.Run
的return类型是Task<Dummy>
。
async () => { return ###Task<Dummy>### }
的类型是Func<Task<Task<Dummy>>>
。注意嵌套任务。所以,same overload of Task.Run
又被调用了,但是这次它的return类型是Task<Task<Dummy>>
。
现在,您只需重复每个级别。 async () => { return ###Task<Task<Dummy>>### }
的类型是 Func<Task<Task<Task<Dummy>>>>
。再次调用same overload of Task.Run
,但这次它的return类型是Task<Task<Task<Dummy>>>
。等等。
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 ?
谁在乎呢?没有任何真实世界的代码会这样做。有一些众所周知的场景,编译器很难在合理的时间范围内处理; using lambda expressions in method overload resolution is one. Nested calls using lambda expressions makes the compiler work exponentially harder.
考虑以下 C# 代码
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;
======================================== ==============================
乍一看,L1、L2、L3都很像
Task<Task<Task<Task<Dummy>>>>
事实证明,L1 和 L2 只是 Task<Dummy>
所以,我在 MSDN 上查找 Task.Run
(我的重载 Task.Run 是:Task.Run<TResult>(Func<Task<TResult>>)
)
MSDN 说:
Return Value is: A Task(TResult) that represents a proxy for the Task(TResult) returned by function.
备注里也写着:
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.
所以,看起来我不应该在我的代码中使用这个重载的 Task.Run,
如果我这样做了,编译器会去掉 Task<Task<Task<...<TResult>>>>
的额外层,然后简单地给你一个 Task<TResult>
如果您将鼠标悬停在 L1 和 L2 上,它会告诉您它是 Task<Dummy>
,而不是我
预期的 Task<Task<Task<Task<Dummy>>>>
到目前为止一切顺利,直到我看到 L3
L3 看起来几乎与 L1 和 L2 完全一样。区别在于:
L3 只有 async
关键字,没有 await
,不像 L1 和 L2,L1 两个都没有,L2 都有
令人惊讶的是,L3 现在被视为 Task<Task<Task<Task<Dummy>>>>
,不像 L1 和 L2,两者都被视为 Task<Dummy>
我的问题:
1。
是什么导致编译器将 L3 与 L1 和 L2 区别对待。为什么简单地将 'async'
添加到 L1(或从 L2 中删除 await
)会导致编译器以不同方式对待它?
2。
如果将更多 Task.Run 级联到 L1/L2/L3,Visual Studio 会崩溃。我正在使用 VS2013,如果我级联 5 层或更多层 Task.Run,它会崩溃。 4 层是我能得到的最好的,这就是我使用 4 层作为示例的原因。只有我吗 ?
编译器在翻译 Task.Run 时发生了什么?
谢谢
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 ?
因为 async
和 await
更改了 lambda 表达式中的类型。您可以将 async
视为 "adding" 一个 Task<>
包装器,将 await
视为 "removing" 一个 Task<>
包装器。
只考虑最内层调用涉及的类型。首先,L1:
return Task.Run(() =>
{
return new Dummy();
});
() => { return new Dummy(); }
的类型是Func<Dummy>
,所以that overload of Task.Run
的return类型是Task<Dummy>
。
所以() => ###Task<Dummy>###
的类型是Func<Task<Dummy>>
,调用了different overload of Task.Run
,return的类型是Task<Dummy>
。等等。
现在考虑 L2:
return await Task.Run(async () =>
{
return new Dummy();
});
async () => { return new Dummy(); }
的类型是Func<Task<Dummy>>
,所以that overload of Task.Run
的return类型是Task<Dummy>
。
async () => await ###Task<Dummy>###
的类型是Func<Task<Dummy>>
,所以调用same overload of Task.Run
的结果类型是Task<Dummy>
。等等。
最后,L3:
return Task.Run(async () =>
{
return new Dummy();
});
async () => { return new Dummy(); }
的类型又是Func<Task<Dummy>>
,所以that overload of Task.Run
的return类型是Task<Dummy>
。
async () => { return ###Task<Dummy>### }
的类型是Func<Task<Task<Dummy>>>
。注意嵌套任务。所以,same overload of Task.Run
又被调用了,但是这次它的return类型是Task<Task<Dummy>>
。
现在,您只需重复每个级别。 async () => { return ###Task<Task<Dummy>>### }
的类型是 Func<Task<Task<Task<Dummy>>>>
。再次调用same overload of Task.Run
,但这次它的return类型是Task<Task<Task<Dummy>>>
。等等。
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 ?
谁在乎呢?没有任何真实世界的代码会这样做。有一些众所周知的场景,编译器很难在合理的时间范围内处理; using lambda expressions in method overload resolution is one. Nested calls using lambda expressions makes the compiler work exponentially harder.