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

230
27 апреля 2017, 12:45

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

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

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

Answer 1

описание в разделе Generalized async return types

Возврат Task из асинхронных методов, в некоторых случаях может стать узким местом в производительности. Task – это ссылочный тип, поэтому при его использовании память под объект будет выделяться в куче. В случаях, когда метод с модификатором async возвращает кэшированный результат, или выполняется синхронно, дополнительное выделение памяти в куче может занимать значительное время в критических секциях кода. Это может стать очень дорогим, если данные выделения будут происходить в циклах.

новые возможности языка позволяют возвращать из асинхронных методов другие типы, кроме Task, Task<T> и void. Возвращаемый тип по прежнему должен удовлетворять асинхронному шаблону, то есть должен быть доступен метод GetAwaiter. В качестве конкретного примере в .NET framework был добавлен тип ValueTask для использования новой возможности:

public async ValueTask<int> Func()
{
    await Task.Delay(100);
    return 5;
}

Простой оптимизацией может стать использование ValueTask там, где ранее использовался Task. Однако, если хочется добавить дополнительную оптимизацию вручную, можно кэшировать результаты асинхронной работы и использовать эти результаты в последующих вызовах. У структуры ValueTask есть конструктор принимающий Task в качестве параметра, так что можно создать ValueTask из возвращаемого значения любого существующего асинхронного метода:

public ValueTask<int> CachedFunc()
{
    return (cache) ? new ValueTask<int>(cacheResult) : new ValueTask<int>(LoadCache());
}
private bool cache = false;
private int cacheResult;
private async Task<int> LoadCache()
{
    // simulate async work:
    await Task.Delay(100);
    cacheResult = 100;
    cache = true;
    return cacheResult;
}

Как и в случаях со всеми рекомендациями по производительности, перед внесением в код масштабных изменений следует сравнить результаты обоих подходов.

Answer 2

Небольшое дополнение к ответу @Grundy и ответ на комментарий @Qutrix:

Для того, чтобы компилятор разрешил использовать ваш собственный Task-подобный тип в качестве возвращаемого значения для асинхронных методов необходимо проделать следующие действия:

  1. Создать этот тип. Он может быть классом или структурой:

    public class MyTask { }
    
  2. Пометить его атрибутом [AsyncMethodBuilder(typeof(MyTaskBuilder))]
  3. Реализовать класс/структуру MyTaskBuilder. В дальнейшем он будет использоваться компилятором для конструирования вашего Task-подобного типа (минимальный набор методов и свойств указан ниже и в обязательном порядке должен присутствовать в определении MyTaskBuilder)

public class MyTaskBuilder
{
    public static MyTaskBuilder Create() => new MyTaskBuilder();
    public void Start<TStateMachine>(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<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine { }
    public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter,
        ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion
        where TStateMachine : IAsyncStateMachine { }
}
  1. И, наконец, для поддержки оператора 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(() => { });
}
READ ALSO
загрузка структуры дерева TreeView из XML

загрузка структуры дерева TreeView из XML

Я пытаюсь отобразить элементы из XML в TreeView используя wpf MVVM

305
генератор строк [требует правки]

генератор строк [требует правки]

Возможно туповатый вопрос но все таки его задам: как можно с генерировать строку чтобы она совпала с той, которую я ввел через ConsoleReadLine(); ? Или...

189