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

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

Ответ 1

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


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

Ответ 2

Чтобы добавить к ответ 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;

Ответ 3

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

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

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

Ответ 4

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

Мы работаем над компилятором, и это очищает его:

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

Здесь собраны лучшие из всех советов, которые здесь представлены. Операция возврата не требуется, если вы фактически не делаете что-то в этом методе.

Ответ 5

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

    public async Task WillBeLongRunningAsyncInTheMajorityOfImplementations()
    {
    }

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

Ответ 6

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

Task.FromResult<MyClass>(null);

Ответ 7

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