首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >将Task.Run与异步/等待关键字混合时的奇怪编译器行为

将Task.Run与异步/等待关键字混合时的奇怪编译器行为
EN

Stack Overflow用户
提问于 2015-02-06 02:16:14
回答 1查看 302关注 0票数 3

考虑以下C#代码

代码语言:javascript
运行
复制
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>

所以,我查找Task.Run的MSDN

(我的重载Task.Run是:Task.Run<TResult>(Func<Task<TResult>>))

MSDN说:

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

它还在评论中说:

语言编译器使用Run<TResult>(Func<Task<TResult>>)方法来支持异步和等待关键字。它不打算直接从用户代码中调用。

所以,看起来我不应该在代码中使用这个重载的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都没有,L2也没有。

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

我的问题:

是什么导致编译器将L3与L1和L2区别对待。为什么简单地将'async'添加到L1 (或将await从L2中删除)会导致编译器对它有不同的对待?

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

谢谢

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2015-02-06 02:41:31

是什么导致编译器将L3与L1和L2区别对待。为什么简单地将‘异步’添加到L1 (或从L2中移除等待)会导致编译器以不同的方式对待它?

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

只需考虑与最内部呼叫有关的类型。首先,L1:

代码语言:javascript
运行
复制
return Task.Run(() =>
{
  return new Dummy();
});

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

因此,() => ###Task<Dummy>###的类型是Func<Task<Dummy>>,它调用Task.Run,返回类型为Task<Dummy>。诸若此类。

现在考虑一下L2:

代码语言:javascript
运行
复制
return await Task.Run(async () =>
{
  return new Dummy();
});

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

async () => await ###Task<Dummy>###的类型是Func<Task<Dummy>>,因此它使用结果类型Task<Dummy>调用Task.Run。诸若此类。

最后,L3:

代码语言:javascript
运行
复制
return Task.Run(async () =>
{
  return new Dummy();
});

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就会崩溃。我使用的是VS2013,如果我级联5层或更多层的Task.Run,它就会崩溃。4层是我能得到的最好的,这就是为什么我使用4层作为我的例子。就我一个人吗?在翻译Task.Run时编译器会发生什么?

谁在乎?任何现实世界的代码都不会这么做。有一些众所周知的场景,编译器很难在合理的时间框架内处理;在方法重载解析中使用lambda表达式是一种。使用lambda表达式使编译器更难成倍地工作。嵌套调用。

票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/28357489

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档