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

Jac*_*Lee 3 .net c# 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;
Run Code Online (Sandbox Code Playgroud)


================================================== ====================
乍一看,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关键字,而不是awaitL1和L2,其中L1既没有它们也有L2都有它们

令人惊讶的是,现在认为L3 Task<Task<Task<Task<Dummy>>>>与L1和L2不同,两者都被考虑在内Task<Dummy>

我的问题:
1.
是什么导致编译器以不同于L1和L2的方式处理L3.为什么简单地添加'async'到L1(或await从L2中删除)会导致编译器对其进行不同的处理?


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

谢谢

Ste*_*ary 6

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

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

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

return Task.Run(() =>
{
  return new Dummy();
});
Run Code Online (Sandbox Code Playgroud)

() => { return new Dummy(); }is 的类型Func<Dummy>,以及该重载Task.Run的返回类型是Task<Dummy>.

所以() => ###Task<Dummy>###is 的类型Func<Task<Dummy>>,调用不同的重载Task.Run,返回类型为Task<Dummy>.等等.

现在考虑L2:

return await Task.Run(async () =>
{
  return new Dummy();
});
Run Code Online (Sandbox Code Playgroud)

类型async () => { return new Dummy(); }Func<Task<Dummy>>,所以该重载Task.Run的返回类型是Task<Dummy>.

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

最后,L3:

return Task.Run(async () =>
{
  return new Dummy();
});
Run Code Online (Sandbox Code Playgroud)

类型async () => { return new Dummy(); }又是Func<Task<Dummy>>,所以该重载Task.Run的返回类型是Task<Dummy>.

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

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

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

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