如果我的接口必须返回Task,那么最好的方式是什么来实现一个无操作的实现?

556
在下面的代码中,由于接口的缘故,类LazyBar 必须从其方法返回任务(出于论证目的不能更改)。如果LazyBar 的实现不同寻常,它可以快速且同步运行 - 那么从该方法返回无操作任务的最佳方法是什么?
我选择了下面的Task.Delay(0),但我想知道如果函数被频繁调用(为论证目的,假设每秒调用数百次),是否会有任何性能副作用:
- 这种语法糖是否会展开为较大的东西? - 它会开始堵塞我的应用程序线程池吗? - 编译器是否足够聪明以不同方式处理 Delay(0)? - 返回 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();
        }
    }
}

2
相关问题:https://dev59.com/i2855IYBdhLWcg3wnFpO创建一个已完成的任务 - CodesInChaos
8
我会选择 Task.FromResult<object>(null) - CodesInChaos
9个回答

787

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


.NET 4.6版本之前:

使用 Task.FromResult(0) 或者 Task.FromResult<object>(null) 比创建空操作表达式的Task更加高效。当创建一个预先确定结果的Task时,不需要进行调度开销。


5
如果你正在使用 https://github.com/StephenCleary/AsyncEx,他们提供了一个 TaskConstants 类来提供这些已完成的任务以及其他几个非常有用的任务(0 整数、true/false、Default<T>())。 - quentin-starin
7
return default(YourReturnType); 翻译为:返回默认值(YourReturnType)。 - Legends
9
@Legends 这并不能直接创建一个任务。 - Reed Copsey
29
我不确定,但 Task.CompletedTask 可能可以解决问题!(但需要 .net 4.6) - Peter
CompletedTask 可以像 Task.FromResult<string>("foo"); 一样使用吗?如果可以,怎么做? - surfmuggle

204

关于使用 Task.FromResult,补充 Reed Copsey的回答,如果您缓存已完成的任务,可以进一步提高性能,因为所有已完成任务的实例都是相同的:

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

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

.Net Framework的最新版本(v4.6)通过Task.CompletedTask静态属性实现了这一点。

Task completedTask = Task.CompletedTask;

我需要将其**返回(return)还是对其进行等待(await)**? - Egor Okhterov
@Pixar 你是什么意思?你可以两者兼顾,但等待它将会同步进行。 - i3arnon
那么,这应该是对Jon问题的答案? - Egor Okhterov
5
@Pixar 我表达不够清楚,我的意思是 "'no-async' 更有效率"。将一个方法设置为 async 会让编译器将其转化为状态机并且每次调用都会创建一个新的任务。返回一个已完成的任务会更清晰和更高效。 - i3arnon
3
它减少了分配(以及随之而来的垃圾收集时间)。每次需要一个已完成的Task时,不再分配新的内存并构造Task实例,只需这样做一次即可。 - i3arnon
显示剩余8条评论

45

Task.Delay(0) 是被接受的答案中好的方法,因为它是一个已完成的 Task 的缓存副本。

自 4.6 版以来,现在有了更明确目的的 Task.CompletedTask,但是 Task.Delay(0) 不仅返回一个单个缓存实例,而且返回与 Task.CompletedTask 相同的单个缓存实例。

两者都不保证其缓存性质始终保持不变,但作为依赖于实现的优化(也就是说, 如果实现更改为仍然有效的内容,则仍会正确工作),使用 Task.Delay(0) 比接受的答案更好。


1
我仍在使用4.5版本,当我进行一些研究时,我惊讶地发现Task.Delay(0)被特殊处理为返回一个静态的CompletedTask成员。然后我将其缓存在自己的静态CompletedTask成员中。 :P - Darren Clark
2
我不知道为什么,在PCL项目中无法使用Task.CompletedTask,即使我将.net版本设置为4.6(profile 7),在VS2017中进行了测试。 - Felix
@Fay 我猜它可能不是 PCL API 表面的一部分,尽管目前唯一支持 PCL 的东西也支持 4.5,所以我已经不得不使用自己的 Task.CompletedTask => Task.Delay(0); 来支持它,所以我不确定它是否可以轻松实现。 - Jon Hanna

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

1
return 对我没用 - 对于一个 async 的任务,我必须使用 await Task.CompletedTask - dylanh724
@dylanh724 我认为这取决于你的函数定义,原始问题要求返回某些内容,因此 return Task.CompletedTask 应该有所帮助。但是,如果你的函数定义可能返回 void,则可以使用 await - Xin

29

最近遇到了这个问题,一直收到关于该方法为void的警告/错误。

我们的业务是要让编译器满意,这样可以解决问题:

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

这里汇集了迄今为止所有建议中最好的部分。除非您在方法中实际执行某些操作,否则不需要返回语句。


27
那完全是错误的。你之所以收到编译器错误,是因为方法定义包含 async,所以编译器期望有一个 await。 "正确" 的用法应该是 public Task MyVoidAsyncMethog() { return Task.CompletedTask; }。 - Keith
3
不确定为什么这个回答会被踩,因为它似乎是最简洁的答案。 - webwake
4
由于Keith的评论。 - noelicus
5
他并没有完全错误,只是删掉了异步关键字。我的方法更符合惯用语。他的方法则比较简洁。有点粗鲁的感觉。 - Alexander Trauzzi
4
完全同意Keith的观点,这毫无意义,我其实并不理解为什么会有这么多点赞。为什么要添加不必要的代码呢?public Task MyVoidAsyncMethod() {}与上面的方法完全相同。如果确实有使用这种方式的用例,请添加额外的代码。 - Nick N.
显示剩余2条评论

15

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

Task.FromResult<MyClass>(null);

2

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

    public async Task WillBeLongRunningAsyncInTheMajorityOfImplementations()
    {
    }

您会收到一个警告(CS1998 - 异步函数没有等待表达式),但在这种情况下可以安全地忽略它。

1
如果你的方法返回void,那么你可能会遇到异常问题。 - Adam Tuliper

-1
return await Task.FromResult(new MyClass());

5
虽然这段代码可能解决了问题,但包括一个解释来说明为什么以及如何解决问题将有助于提高您帖子的质量,并可能导致更多的赞。请记住,您回答问题是为了未来的读者而不仅仅是现在提问的人。请[编辑]您的答案以添加解释,并指出适用的限制和假设。 - David Buck

-1

如果您正在使用泛型,所有答案都会给我们编译错误。 您可以使用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);
                }

            }

这个问题不是关于异步方法的 :) - Frode Nilsen

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