将Task.Run与async / await关键字混合时的奇怪编译器行为

时间:2015-02-06 02:16:15

标签: c# .net asynchronous async-await

考虑以下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说:

  

返回值是:一个任务(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时会发生什么?

由于

1 个答案:

答案 0 :(得分:6)

  

是什么导致编译器以不同于L1和L2的方式处理L3。为什么简单地添加&#39; async&#39;到L1(或从L2中删除await)导致编译器以不同的方式处理它?<​​/ p>

因为asyncawait会更改lambda表达式中的类型。您可以将async视为&#34;添加&#34;一个Task<>包装,await为&#34;删除&#34;一个Task<>包装器。

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

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

() => { return new Dummy(); }的类型为Func<Dummy>,因此that overload of Task.Run的返回类型为Task<Dummy>

因此() => ###Task<Dummy>###的类型为Func<Task<Dummy>>,其调用different overload of Task.Run,返回类型为Task<Dummy>。等等。

现在考虑L2:

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

async () => { return new Dummy(); }的类型为Func<Task<Dummy>>,因此that overload of Task.Run的返回类型为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的返回类型为Task<Dummy>

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

现在,您只需重复每个级别。 async () => { return ###Task<Task<Dummy>>### }的类型为Func<Task<Task<Task<Dummy>>>>。再次调用same overload of Task.Run,但此次返回类型为Task<Task<Task<Dummy>>>。等等。

  

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

谁在乎?没有现实世界的代码可以做到这一点。众所周知的场景对于编译器来说在合理的时间范围内难以处理; using lambda expressions in method overload resolution is one。使用lambda表达式嵌套调用makes the compiler work exponentially harder