it-roy-ru.com

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

В приведенном ниже коде из-за интерфейса класс LazyBar должен возвращать задачу из своего метода (и ради аргументов не может быть изменен). Если реализация LazyBars необычна в том смысле, что она выполняется быстро и синхронно - каков наилучший способ вернуть задачу No-Operation из метода?

Я описал Task.Delay(0) ниже, однако я хотел бы знать, есть ли у этого какие-либо побочные эффекты производительности, если функция называется lot (для аргументов, скажем, сотни раз в секунду):

  • Этот синтаксический сахар раскручивается к чему-то большому?
  • Это начинает засорять пул потоков моего приложения?
  • Достаточно ли ясен компилятор, чтобы по-разному справляться с 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();
        }
    }
}
358
Jon Rea

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


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

518
Reed Copsey

Чтобы добавить к ответ Рида Копси об использовании 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;
168
i3arnon

Task.Delay(0) как в принятом ответе был хорошим подходом, так как это кэшированная копия заполненного Task.

Начиная с версии 4.6 теперь есть Task.CompletedTask, который является более явным по своему назначению, но не только Task.Delay(0) по-прежнему возвращает один кэшированный экземпляр, он возвращает тот же один кэшированный экземпляр, как и Task.CompletedTask.

Кэшируемая природа ни того, ни другого гарантированно не останется постоянной, но в качестве зависящих от реализации оптимизаций, которые зависят только от реализации в качестве оптимизаций (то есть они все равно будут работать правильно, если реализация изменится на что-то еще действительное), используется Task.Delay(0) был лучше, чем принятый ответ.

32
Jon Hanna

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

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

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

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

14
Alexander Trauzzi

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

    public async Task WillBeLongRunningAsyncInTheMajorityOfImplementations()
    {
    }

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

3
Remco te Wierik

Когда вы должны вернуть указанный тип:

Task.FromResult<MyClass>(null);
1
trashmaker_