等待Task.CompletedTask的目的是什么?

41

我使用在Build2017上发布的Windows Template Studio创建了一个UWP应用。

下面的代码是由它生成的一部分。

public class SampleModelService
{
    public async Task<IEnumerable<SampleModel>> GetDataAsync()
    {
        await Task.CompletedTask; // <-- what is this for?
        var data = new List<SampleModel>();

        data.Add(new SampleModel
        {
            Title = "Lorem ipsum dolor sit 1",
            Description = "Lorem ipsum dolor sit amet",
            Symbol = Symbol.Globe
        });

        data.Add(new SampleModel
        {
            Title = "Lorem ipsum dolor sit 2",
            Description = "Lorem ipsum dolor sit amet",
            Symbol = Symbol.MusicInfo
        });
        return data;
    }
}

我的问题是,这里的await Task.CompletedTask;代码的目的和原因是什么?它实际上没有从中接收Task结果。


1
尽管在相关线程中有关于抛出异常如何影响异步方法的问题,但可以将这视为一种抑制警告 CS1998:此异步方法缺少 'await' 运算符并将同步运行 的方式。 - Jeppe Stig Nielsen
3个回答

50

这样做是为了方便后续阶段实现异步代码调用而无需修改签名,从而避免重构调用代码。

虽然样板代码是同步的,但模板工作室专门设计了一个异步数据访问层,您需要通过修改生成的方法的主体来实现自己的数据访问。

如果异步实现未实现,则整个模板应用程序将需要进行大量代码更改,并且对于新开发人员来说,学习曲线非常陡峭,而这个模板的目的是最小化努力甚至经验!

另一种选择是从方法签名和该行中删除async关键字,然后执行

return Task.FromResult<IEnumerable<SampleModel>>(data); 

当你需要返回一个可等待的任务(awaitable Task),但是实现没有异步工作需要完成时,例如在使用接口时,你会看到这种结构。

然而,在这种情况下,由于它是一个模板,他们期望人们将await Task.Completed 替换为类似 await FetchDataFromDatabaseAsync();。由于async关键字已经存在,它最小化了实现自己的异步调用所需的更改量。

无论如何,如果没有这个await结构,你可以这样做:

public class SampleModelService
{
    public Task<IEnumerable<SampleModel>> GetDataAsync()
    {
        var data = new List<SampleModel>();

        data.Add(new SampleModel
        {
            Title = "Lorem ipsum dolor sit 1",
            Description = "Lorem ipsum dolor sit amet",
            Symbol = Symbol.Globe
        });

        data.Add(new SampleModel
        {
            Title = "Lorem ipsum dolor sit 2",
            Description = "Lorem ipsum dolor sit amet",
            Symbol = Symbol.MusicInfo
        });

        return Task.FromResult<IEnumerable<SampleModel>>(data); 
     }
}

如果完全没有返回 Task 的要求(你没有任何异步代码),那就将其完全移除。(但是你必须重构调用此方法的代码)

通过审查这段代码,我怀疑后面在开发过程中会有人调用异步方法,并已经预见到这一点,因此指定了该方法返回一个 Task


1
那么我们可以说,它是 async 签名的一种占位符。对吗? - Youngjae
5
每当您使用 await 等待某个操作完成时,都需要在方法签名中添加 async。有关为什么必须使用 async 关键字的更深入的解释,请阅读此帖子:https://blogs.msdn.microsoft.com/ericlippert/2010/11/11/asynchrony-in-c-5-part-six-whither-async/。在这种特定情况下,正是为了添加 async 关键字而进行的辩护。由于这是一个模板,他们期望人们将 await Task.Completed 替换为类似于 await FetchDataFromDatabaseAsync(); 的内容。 - Peter Bons
返回 Task.Run(()=>{xx })? - lindexi
消费者对异常处理语义会有期望吗?如果代码抛出异常会发生什么?通常情况下,它应该返回一个故障任务,但在这种情况下不会;异常将会向上冒泡。 - rory.ap
我已经数不清有多少次我删除了async关键字,然后又把它放回去,只是稍后再次将其删除。对于Task.FromResult函数调用也是如此。实际上,这似乎是一种避免所有麻烦的好方法。只需首先设计异步,并在处理非异步代码时使用await Task.CompletedTask即可。 - emt
显示剩余2条评论

2

await Task.CompletedTask 使得像第一个答案所说的那样更容易实现这个方法。由于这种实现不耗时,因此 "await Task.CompletedTask;" 或者 "return Task.FromResult(data);" 都会在同步运行。你可以使用上述两种模式之一,但是这个方法被设计为异步方法,因此当你的实现很耗时但是没有任何异步代码时,请使用 Task.Run 来创建一个任务。


0

该方法被声明为async。因此编译器期望它await某些东西,比如对外部数据库的调用。

您的代码是同步的,所以编译器会警告您存在“代码异味”。您可以通过添加await Task.CompletedTask来抑制此警告 - 这不会做任何事情,但编译器会满意。

如果您有一个条件await,例如当您检查方法参数或外部绑定尚未初始化时,这也将非常有用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接