繁体   English   中英

将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 for Task.Run
(我重载的Task.Run是: Task.Run<TResult>(Func<Task<TResult>>)
MSDN说:

返回值是:一个Task(TResult),表示函数返回的Task(TResult)的代理。

它还在备注中说:

语言编译器使用Run<TResult>(Func<Task<TResult>>)方法来支持async和await关键字。 它无意直接从用户代码调用。


所以,看起来我不应该在我的代码中使用这个重载的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。
是什么导致编译器以不同于L1和L2的方式处理L3。 为什么简单地将'async'添加到L1(或从L2中删除await )会导致编译器对其进行不同的处理?


2。
如果您将更多Task.Run级联到L1 / L2 / L3,则Visual Studio会崩溃。 我正在使用VS2013,如果我级联5层或更多层的Task.Run,​​它会崩溃。 4层是我能得到的最好的,这就是为什么我使用4层作为我的例子。 只有我吗 ? 编译器在翻译Task.Run时会发生什么?

谢谢

是什么导致编译器以不同于L1和L2的方式处理L3。 为什么简单地将“async”添加到L1(或从L2中删除等待)会导致编译器对其进行不同的处理?

因为asyncawait更改了lambda表达式中的类型。 您可以将async视为“添加” Task<>包装器,并await “删除” Task<>包装器。

只需考虑最里面调用中涉及的类型。 首先,L1:

return Task.Run(() =>
{
  return new Dummy();
});

() => { return new Dummy(); } () => { return new Dummy(); }Func<Dummy>等的返回类型的那超负荷Task.RunTask<Dummy>

因此() => ###Task<Dummy>###Func<Task<Dummy>> ,它调用Task.Run不同重载,返回类型为Task<Dummy> 等等。

现在考虑L2:

return await Task.Run(async () =>
{
  return new Dummy();
});

async () => { return new Dummy(); } async () => { return new Dummy(); }Func<Task<Dummy>> ,所以返回类型的那超负荷Task.RunTask<Dummy>

async () => await ###Task<Dummy>###的类型async () => await ###Task<Dummy>###Func<Task<Dummy>> ,因此它调用Task.Run相同重载,结果类型为Task<Dummy> 等等。

最后,L3:

return Task.Run(async () =>
{
  return new Dummy();
});

async () => { return new Dummy(); } async () => { return new Dummy(); }再次是Func<Task<Dummy>> ,所以返回类型的那超负荷Task.RunTask<Dummy>

async () => { return ###Task<Dummy>### }Func<Task<Task<Dummy>>> 请注意嵌套任务。 因此,再次调用Task.Run相同重载 ,但Task.Run返回类型为Task<Task<Dummy>>

现在,您只需重复每个级别。 async () => { return ###Task<Task<Dummy>>### }Func<Task<Task<Task<Dummy>>>> 再次调用Task.Run相同重载 ,但这次它的返回类型是Task<Task<Task<Dummy>>> Task.Run 等等。

如果您将更多Task.Run级联到L1 / L2 / L3,则Visual Studio会崩溃。 我正在使用VS2013,如果我级联5层或更多层的Task.Run,​​它会崩溃。 4层是我能得到的最好的,这就是为什么我使用4层作为我的例子。 只有我吗 ? 编译器在翻译Task.Run时会发生什么?

谁在乎? 没有现实世界的代码可以做到这一点。 众所周知的场景对于编译器来说在合理的时间范围内难以处理; 在方法重载解析中使用lambda表达式是其中之一 使用lambda表达式的嵌套调用使编译器的工作量指数级增加

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM