В С# 7, как я могу "свернуть свой собственный" тип типа Task, который будет использоваться с async?
Одной из менее обсуждаемых функций С# 7 является "обобщенные типы возврата async", который описан Microsoft как:
Возвращение объекта Task из методов async может привести к узким местам производительности в определенных путях. Задача является ссылочным типом, поэтому ее использование означает выделение объекта. В тех случаях, когда метод, объявленный с помощью модификатора async, возвращает результат кэширования или завершает синхронно, дополнительные распределения могут стать значительными временными затратами в критических разделах кода производительности. Это может стать очень дорогостоящим, если эти распределения происходят в плотных циклах.
Новая функция языка означает, что методы async могут возвращать другие типы в дополнение к Task
, Task<T>
и void
. Возвращаемый тип должен удовлетворять шаблону async, то есть метод GetAwaiter должен быть доступен. В качестве одного из конкретных примеров тип ValueTask был добавлен в среду .NET для использования этой новой языковой функции:
Это звучит здорово, но я не могу на всю жизнь найти какой-нибудь пример, который не просто использует тип запаса ValueTask<T>
. Я хочу создать свой собственный тип типа Task. В частности, я хочу тип, который ведет себя как Task<T>
, но с более функциональным стилем обработки ошибок.
Вот тип, который я использую для обработки функциональных ошибок в моем проекте:
public class Try<T> {
public T Data { get; }
public Exception Error { get; }
public bool HasData => Error == null;
public bool HasError => Error != null;
public Try(T data) {
Data = data;
}
public Try(Exception error) {
Error = error;
}
}
Вот что я думаю, что мой пользовательский ожидаемый тип должен выглядеть так:
public class TryTask<T> : Task<Try<T>> {
public TryTask(Func<Try<T>> func)
: base(func) { }
//GetAwaiter is defined on base type, so we should be okay there
}
Все это компилируется, пока я не попытаюсь использовать его как возвращаемый тип async:
async TryTask<int> DoWhatever() {
return await new TryTask<int>(() => new Try<int>(1));
}
Этот метод даст ошибку компилятора. Тип возврата метода async должен быть недействительным, Задача или Задача.
Как мне сделать это или что-то вроде этого компилировать?
Update:
Чтобы подтвердить, я использую выпуск VS 2017 от 3/7, и я могу использовать другие функции С# 7 в моем проекте, такие как локальные функции.
Я также пробовал использовать ValueTask
и получаю ту же ошибку компилятора.
static async ValueTask<int> DoWhatever() {
return await new ValueTask<int>(1);
}
Вот еще одно сообщение, которое проливает свет на то, что происходит.
Как получить новую семантику async, работающую в VS2017 RC?
По-видимому, должен быть определен отдельный тип "построитель метода", и специальные атрибуты должны применяться к ожидаемому типу. Я не знаю, есть ли у меня время, чтобы вникать в это. Это скорее похоже на метапрограммирование хакера, чем "языковая функция".
Ответы
Ответ 1
Я еще не нашел хорошего учебника.
Но вы можете посмотреть unittests компилятора, которые создают такие типа типа задач (ищите "[AsyncMethodBuilder" ).
Отправной точкой является создание типа и пометка его как задачи с атрибутом типа [AsyncMethodBuilder(typeof(MyTaskBuilder))]
.
Затем вам нужно определить свой собственный тип MyTaskBuilder
. Он должен реализовать определенный шаблон (см. Ниже). Это тот же шаблон, который реализуется с помощью обычного AsyncMethodBuilder
, который поддерживает обычный Task
.
class MyTaskBuilder
{
public static MyTaskBuilder Create() => null;
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 => default(MyTask);
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 { }
}
Обновление: малая спецификация для типа задач была добавлена в документы компилятора.
Ответ 2
Для потомков блог Dixin имеет фантастическую запись с примером.
https://weblogs.asp.net/dixin/functional-csharp-asynchronous-function
Блог Диксина - сокровищница.
Кроме того, здесь есть реальный пример реализации:
https://github.com/nessos/Eff/blob/master/src/Eff.Core/Eff.cs
https://github.com/nessos/Eff/blob/master/src/Eff.Core/EffMethodBuilder.cs