Если мой интерфейс должен вернуть Task, каков наилучший способ реализации без операции?


436

В приведенном ниже коде из-за интерфейса класс LazyBarдолжен возвращать задачу из своего метода (и ради аргументов не может быть изменен). Если LazyBarреализация s необычна в том смысле, что она выполняется быстро и синхронно - каков наилучший способ вернуть задачу 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

Ответы:


626

Использование Task.FromResult(0)или Task.FromResult<object>(null)приведет к меньшим накладным расходам, чем создание Taskс выражением no-op. При создании Taskс заранее определенным результатом не требуются затраты на планирование.


Сегодня я бы рекомендовал использовать Task.CompletedTask для достижения этой цели.


5
И если вы используете github.com/StephenCleary/AsyncEx, они предоставляют класс TaskConstants для предоставления этих завершенных задач вместе с несколькими другими весьма полезными (0 int, true / false, Default <T> ())
quentin-starin

5
return default(YourReturnType);
Легенды

8
@Legends Это не работает для создания задачи напрямую
Рид Копси

18
Я не уверен, но Task.CompletedTaskмог бы добиться цели! (но требует .net 4.6)
Питер

187

Чтобы добавить к ответу Рида Копси об использовании Task.FromResult, вы можете еще больше повысить производительность, если кешируете уже выполненную задачу, поскольку все экземпляры выполненных задач одинаковы:

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

С помощью TaskExtensions.CompletedTaskвы можете использовать один и тот же экземпляр во всем домене приложения.


В последней версии .Net Framework (v4.6) добавлено только это со Task.CompletedTaskстатическим свойством

Task completedTask = Task.CompletedTask;

Нужно ли возвращать или ждать ?
Pixar

@ Пиксар, что ты имеешь в виду? Вы можете сделать и то и другое, но ожидание будет продолжаться синхронно.
i3arnon

Извините, я должен был упомянуть контекст :) Как я вижу это сейчас, мы можем сделать public Task WillBeLongRunningAsyncInTheMajorityOfImplementations()так же, как public async Task WillBeLongRunningAsyncInTheMajorityOfImplementations(). Итак, мы можем return CompletedTask;или await CompletedTask;. Что является более предпочтительным (возможно, более эффективным или более конгруэнтным)?
Pixar

3
@ Пиксар, я не понимаю. Я имел в виду, что «без асинхронности будет более эффективно». Создание метода async указывает компилятору преобразовать его в конечный автомат. Он также будет создавать новую задачу каждый раз, когда вы ее вызываете. Возврат уже выполненного задания будет более понятным и более эффективным.
i3arnon

3
@ Asad это уменьшает распределение (и вместе с этим время GC). Вместо выделения новой памяти и создания экземпляра Задачи каждый раз, когда вам нужна завершенная Задача, вы делаете это только один раз.
i3arnon

38

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
Даррен Кларк

2
Я не знаю почему, но Task.CompletedTaskне могу использоваться в проекте PCL, даже если я установил версию .net на 4.6 (профиль 7), только что протестированный в VS2017.
Феликс

@Fay, я думаю, он не должен быть частью поверхности API PCL, хотя единственное, что в данный момент делает что-то, что поддерживает PCL, также поддерживает 4.5, так что я уже должен использовать свой собственный, Task.CompletedTask => Task.Delay(0);чтобы поддержать это, поэтому я не не знаю наверняка с макушки головы.
Джон Ханна

17

Недавно столкнулся с этим и продолжал получать предупреждения / ошибки о недействительности метода.

Мы успокаиваем компилятор, и это проясняет ситуацию:

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

Это объединяет лучшие из всех советов здесь до сих пор. Оператор return не требуется, если вы на самом деле не делаете что-то в методе.


17
Это совершенно неправильно. Вы получаете ошибку компилятора, потому что определение метода содержит асинхронный, поэтому компилятор ожидает ожидания. «Правильное» использование будет общедоступным Task MyVoidAsyncMethog () {return Task.CompletedTask;}
Кейт,

3
Не уверен, почему за это проголосовали, так как это кажется самым чистым ответом
webwake

3
Из-за комментария Кейта.
noelicus

4
Он не совсем неправ, он просто удалил ключевое слово async. Мой подход более идиоматичен. Его минималистично. Если не немного грубо.
Александр Трауцци

1
Это не имеет никакого смысла, полностью согласен с Китом здесь, на самом деле я не получаю всех голосов. Зачем вам добавлять код, который не нужен? public Task MyVoidAsyncMethod() {}полностью такой же, как описанный выше метод. Если есть такой вариант использования, добавьте дополнительный код.
Ник Н.



3

Я предпочитаю Task completedTask = Task.CompletedTask;решение .Net 4.6, но другой подход - пометить метод async и вернуть void:

    public async Task WillBeLongRunningAsyncInTheMajorityOfImplementations()
    {
    }

Вы получите предупреждение (CS1998 - Асинхронная функция без выражения await), но это безопасно игнорировать в этом контексте.


1
Если ваш метод возвращает void, у вас могут возникнуть проблемы с исключениями.
Адам Тюльпер - 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);
                }

            }

Почему отрицательный голос?
Картикеян ВК

Вопрос не был об асинхронных методах :)
Фроде Нильсен

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

3
Хотя этот код может решить вопрос, в том числе объяснение того, как и почему это решает проблему, действительно поможет улучшить качество вашего сообщения и, вероятно, получит больше голосов "за". Помните, что вы отвечаете на вопрос для читателей в будущем, а не только для того, кто спрашивает сейчас. Пожалуйста, измените свой ответ, чтобы добавить объяснения и указать, какие ограничения и предположения применяются.
Дэвид Бак
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.