Страницы

Поиск по вопросам

четверг, 5 декабря 2019 г.

Когда следует использовать ValueTask<T>?

#c_sharp #net #c_sharp_70


В новой редакции языка появилось новшество ValueTask .

Правильно ли я понимаю, что его следует использовать в том случае, когда есть вероятность,
что задача выполнится быстрее,чем я вызову await?

Обычный Task умер или все таки в каких-то случаях его нужно использовать?
    


Ответы

Ответ 1



описание в разделе Generalized async return types Возврат Task из асинхронных методов, в некоторых случаях может стать узким местом в производительности. Task – это ссылочный тип, поэтому при его использовании память под объект будет выделяться в куче. В случаях, когда метод с модификатором async возвращает кэшированный результат, или выполняется синхронно, дополнительное выделение памяти в куче может занимать значительное время в критических секциях кода. Это может стать очень дорогим, если данные выделения будут происходить в циклах. новые возможности языка позволяют возвращать из асинхронных методов другие типы, кроме Task, Task и void. Возвращаемый тип по прежнему должен удовлетворять асинхронному шаблону, то есть должен быть доступен метод GetAwaiter. В качестве конкретного примере в .NET framework был добавлен тип ValueTask для использования новой возможности: public async ValueTask Func() { await Task.Delay(100); return 5; } Простой оптимизацией может стать использование ValueTask там, где ранее использовался Task. Однако, если хочется добавить дополнительную оптимизацию вручную, можно кэшировать результаты асинхронной работы и использовать эти результаты в последующих вызовах. У структуры ValueTask есть конструктор принимающий Task в качестве параметра, так что можно создать ValueTask из возвращаемого значения любого существующего асинхронного метода: public ValueTask CachedFunc() { return (cache) ? new ValueTask(cacheResult) : new ValueTask(LoadCache()); } private bool cache = false; private int cacheResult; private async Task LoadCache() { // simulate async work: await Task.Delay(100); cacheResult = 100; cache = true; return cacheResult; } Как и в случаях со всеми рекомендациями по производительности, перед внесением в код масштабных изменений следует сравнить результаты обоих подходов.

Ответ 2



Небольшое дополнение к ответу @Grundy и ответ на комментарий @Qutrix: Для того, чтобы компилятор разрешил использовать ваш собственный Task-подобный тип в качестве возвращаемого значения для асинхронных методов необходимо проделать следующие действия: Создать этот тип. Он может быть классом или структурой: public class MyTask { } Пометить его атрибутом [AsyncMethodBuilder(typeof(MyTaskBuilder))] Реализовать класс/структуру MyTaskBuilder. В дальнейшем он будет использоваться компилятором для конструирования вашего Task-подобного типа (минимальный набор методов и свойств указан ниже и в обязательном порядке должен присутствовать в определении MyTaskBuilder) public class MyTaskBuilder { public static MyTaskBuilder Create() => new MyTaskBuilder(); public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine { } public void SetStateMachine(IAsyncStateMachine stateMachine) { } public void SetResult() { } public void SetException(Exception exception) { } public MyTask Task { get; } public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine { } public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine { } } И, наконец, для поддержки оператора await так же необходима реализация в классе MyTask открытого метода GetAwaiter(), тип возвращаемого значения (MyTaskAwaiter) которого должен реализовывать интерфейс INotifyCompletion, иметь открытое свойство IsCompleted, и конечно же метод GetResult() (который будет возвращать результат асинхронной операции или же void). [AsyncMethodBuilder(typeof(MyTaskBuilder))] public class MyTask { public static MyTask Run(Action action) { } public MyTaskAwaiter GetAwaiter() { } } public class MyTaskAwaiter : INotifyCompletion { public bool IsCompleted { get; } public void GetResult() { } public void OnCompleted(Action continuation) { } } Таким образом мы получили возможность писать что-то вроде private async MyTask ExecuteAsync() { await MyTask.Run(() => { }); }

Ответ 3



Правильно ли я понимаю, что его следует использовать в том случае, когда есть вероятность, что задача выполнится быстрее,чем я вызову await? В целом верно, но с небольшой поправкой: когда есть вероятность, что задача выполнится без прерывания выполнения. Или, что то же самое, если он может выполниться синхронно. Это не то же самое, что без вызова await, потому что оператор await не прерывает выполнение метода если он вызван над уже завершенной задачей. К примеру, метод ниже будет выполнен синхронно: async ValueTask Foo() { await Task.Completed; await Task.FromResult(1); return await Task.FromResult(false); }

Комментариев нет:

Отправить комментарий