如果我的界面必须返回Task,那么执行无操作实现的最佳方法是什么?


435

在下面的代码中,由于接口的原因,该类LazyBar必须从其方法中返回一个任务(出于参数的考虑,不能更改)。如果LazyBars实现不寻常,因为它恰巧可以快速且同步地运行,那么从该方法返回No-Operation任务的最佳方法是什么?

我已经跟Task.Delay(0)下面,但是我想知道这是否有任何性能的副作用,如果该功能被称为很多(为了讨论各种情形,说上百次),:

  • 这种语法糖是否会发展成大事?
  • 它会开始阻塞我的应用程序的线程池吗?
  • 编译器切割器是否足以应付Delay(0)不同要求?
  • 请问return Task.Run(() => { });有什么不同?

有没有更好的办法?

using System.Threading.Tasks;

namespace MyAsyncTest
{
    internal interface IFooFace
    {
        Task WillBeLongRunningAsyncInTheMajorityOfImplementations();
    }

    /// <summary>
    /// An implementation, that unlike most cases, will not have a long-running
    /// operation in 'WillBeLongRunningAsyncInTheMajorityOfImplementations'
    /// </summary>
    internal class LazyBar : IFooFace
    {
        #region IFooFace Members

        public Task WillBeLongRunningAsyncInTheMajorityOfImplementations()
        {
            // First, do something really quick
            var x = 1;

            // Can't return 'null' here! Does 'Task.Delay(0)' have any performance considerations?
            // Is it a real no-op, or if I call this a lot, will it adversely affect the
            // underlying thread-pool? Better way?
            return Task.Delay(0);

            // Any different?
            // return Task.Run(() => { });

            // If my task returned something, I would do:
            // return Task.FromResult<int>(12345);
        }

        #endregion
    }

    internal class Program
    {
        private static void Main(string[] args)
        {
            Test();
        }

        private static async void Test()
        {
            IFooFace foo = FactoryCreate();
            await foo.WillBeLongRunningAsyncInTheMajorityOfImplementations();
            return;
        }

        private static IFooFace FactoryCreate()
        {
            return new LazyBar();
        }
    }
}


8
我个人会和Task.FromResult<object>(null)
CodesInChaos

Answers:


625

与使用no-op表达式创建相比,使用Task.FromResult(0)Task.FromResult<object>(null)将产生较少的开销Task。创建Task具有预定结果的时,不涉及调度开销。


今天,我建议使用Task.CompletedTask来完成此任务。


5
而且,如果您碰巧正在使用github.com/StephenCleary/AsyncEx,它们会提供TaskConstants类来提供这些完成的任务以及其他一些非常有用的任务(0 int,true / false,Default <T>())
quentin-starin

5
return default(YourReturnType);
Legends

8
@Legends不能直接创建任务
Reed Copsey

18
我不确定,但Task.CompletedTask可能会成功!(但要求使用.net 4.6)
彼得,Peter

1
问题:Task.FromResult<TResult>返回a的that Task<TResult>满足返回类型Task(无类型参数)的情况?
rory.ap

187

要添加到Reed Copsey关于using 的答案Task.FromResult,您可以缓存已完成的任务,从而进一步提高性能,因为所有已完成任务的实例都是相同的:

public static class TaskExtensions
{
    public static readonly Task CompletedTask = Task.FromResult(false);
}

有了TaskExtensions.CompletedTask您可以使用相同的实例在整个应用程序域。


.Net Framework(v4.6)最新版本仅添加了带有Task.CompletedTaskstatic属性的内容。

Task completedTask = Task.CompletedTask;

我需要退货还是等待呢?
皮克斯2015年

@Pixar是什么意思?您可以同时进行,但是等待将继续同步。
i3arnon

对不起,我不得不提的背景:)至于现在我明白了,我们可以public Task WillBeLongRunningAsyncInTheMajorityOfImplementations()public async Task WillBeLongRunningAsyncInTheMajorityOfImplementations()。因此,我们可以选择return CompletedTask;await CompletedTask;。什么是更可取的(可能更有效或更全)?
皮克斯2015年

3
@皮克斯,我不清楚。我的意思是“'非异步'会更有效”。使方法异步指示编译器将其转换为状态机。每当您调用它时,它还将创建一个新任务。返回已经完成的任务将更加清晰和高效。
i3arnon

3
@Asad减少分配(以及GC时间)。每次需要完成的Task时,不必分配新的内存并构造Task实例,而只需执行一次。
i3arnon

38

Task.Delay(0)因为接受的答案是一种很好的方法,因为它是complete的缓存副本Task

从4.6开始Task.CompletedTask,它的目的更加明确,但不仅Task.Delay(0)仍然返回单个缓存的实例,而且还返回相同的单个缓存的实例Task.CompletedTask

既不缓存的本质是保证保持不变,但作为实现相关的优化技术是唯一实现相关的优化技术(也就是,他们还是会正确如果实现改变的东西,仍然是有效的工作)使用的Task.Delay(0)是比公认的答案更好。


1
我仍在使用4.5,当我做一些研究时,我很高兴发现Task.Delay(0)在返回静态静态CompletedTask成员的情况下是特殊情况。然后,我将其缓存在自己的静态CompletedTask成员中。:P
达伦·克拉克

2
我不知道为什么,但是Task.CompletedTask即使我将.net版本设置为4.6(配置文件7)(仅在VS2017中进行了测试),也无法在PCL项目中使用。
菲利克斯(Felix)

@Fay我猜想它一定不能成为PCL API表面的一部分,尽管目前唯一支持PCL的事情还支持4.5,所以我已经不得不使用自己Task.CompletedTask => Task.Delay(0);的支持了,所以我不我不知道该怎么办。
乔恩·汉纳

17

最近遇到这种情况,并不断收到有关该方法无效的警告/错误。

我们负责编译器的工作,这可以清除它:

    public async Task MyVoidAsyncMethod()
    {
        await Task.CompletedTask;
    }

到目前为止,这里汇集了所有建议中最好的。除非您实际上在该方法中进行任何操作,否则不需要return语句。


17
那是完全错误的。因为方法定义包含异步,所以您遇到编译器错误,因此编译器期望等待。“正确的”用法是公共任务MyVoidAsyncMethog(){return Task.CompletedTask;}
Keith,

3
不确定为什么这被否决了,因为这似乎是最干净的答案
webwake 17-4-27

3
因为Keith的评论。
noelicus

4
他不是完全错误,只是删除了async关键字。我的方法比较惯用。他很简约。如果没有一点不礼貌。
Alexander Trauzzi

1
这完全没有道理,在这里完全同意Keith的观点,实际上我没有得到所有的支持。为什么要添加不必要的代码?public Task MyVoidAsyncMethod() {}与上述方法完全相同。如果有这样的用例,请添加其他代码。
尼克N.

12
return Task.CompletedTask; // this will make the compiler happy

9

当您必须返回指定的类型时:

Task.FromResult<MyClass>(null);

3

我更喜欢Task completedTask = Task.CompletedTask;.Net 4.6 的解决方案,但是另一种方法是将方法标记为异步并返回void:

    public async Task WillBeLongRunningAsyncInTheMajorityOfImplementations()
    {
    }

您会收到警告(CS1998-不具有await表达式的Async函数),但是在这种情况下可以忽略。


1
如果您的方法返回void,则可能会出现异常问题。
Adam Tuliper-MSFT

0

如果您使用的是泛型,则所有答案都会给我们带来编译错误。您可以使用return default(T);。下面的示例进一步解释。

public async Task<T> GetItemAsync<T>(string id)
            {
                try
                {
                    var response = await this._container.ReadItemAsync<T>(id, new PartitionKey(id));
                    return response.Resource;
                }
                catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
                {

                    return default(T);
                }

            }

为什么要下票?
Karthikeyan VK

问题不在于异步方法:)
Frode Nilsen

0
return await Task.FromResult(new MyClass());

3
尽管这段代码可以解决问题,但包括解释如何以及为什么解决该问题的说明,确实可以帮助提高您的帖子质量,并可能导致更多的投票。请记住,您将来会为读者回答问题,而不仅仅是现在问的人。请编辑您的答案以添加说明,并指出适用的限制和假设。
David Buck
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.