Как реализовать метод интерфейса, который возвращает Task <T>?
У меня есть интерфейс
interface IFoo
{
Task<Bar> CreateBarAsync();
}
Существует два метода создания Bar
, один асинхронный и один синхронный. Я хочу предоставить реализацию интерфейса для каждого из этих двух методов.
Для асинхронного метода реализация может выглядеть так:
class Foo1 : IFoo
{
async Task<Bar> CreateBarAsync()
{
return await AsynchronousBarCreatorAsync();
}
}
Но КАК я должен реализовать класс Foo2
, который использует синхронный метод для создания Bar
?
Я мог бы реализовать метод для синхронного запуска:
async Task<Bar> CreateBarAsync()
{
return SynchronousBarCreator();
}
Затем компилятор предупреждает об использовании async
в сигнатуре метода:
В этом асинхронном методе отсутствуют операторы "ждут" и будут выполняться синхронно. Подумайте о том, как использовать оператор "ожидание" для ожидания неблокирующих вызовов API или "ждать Task.Run(...)", чтобы выполнять работу с привязкой к процессору в фоновом потоке.
Или я мог бы реализовать метод для явного возврата Task<Bar>
. По-моему, код будет выглядеть менее читаемым:
Task<Bar> CreateBarAsync()
{
return Task.Run(() => SynchronousBarCreator());
}
С точки зрения производительности, я полагаю, что оба подхода имеют одинаковую накладную или?
Какой подход я должен выбрать; внедрить метод async
синхронно или явно обернуть вызов синхронного метода в Task
?
ИЗМЕНИТЬ
Проект, над которым я работаю, действительно представляет собой проект .NET 4 с расширениями async/await из пакета Microsoft Async NuGet. В .NET 4 Task.Run
можно заменить на TaskEx.Run
. Я сознательно использовал метод .NET 4.5 в приведенном выше примере в надежде сделать основной вопрос более понятным.
Ответы
Ответ 1
Когда вам нужно реализовать метод асинхронизации из интерфейса, и ваша реализация является синхронной, вы можете использовать решение Ned:
public Task<Bar> CreateBarAsync()
{
return Task.FromResult<Bar>(SynchronousBarCreator());
}
С помощью этого решения метод выглядит как aync, но является синхронным.
Или предлагаемое решение:
Task<Bar> CreateBarAsync()
{
return Task.Run(() => SynchronousBarCreator());
}
Таким образом, метод действительно асинхронный.
У вас нет общего решения, которое будет соответствовать всем случаям "Как реализовать метод интерфейса, возвращающий задачу". Это зависит от контекста: достаточно ли ваша реализация достаточно, чтобы ссылаться на другой поток бесполезно? Как этот интерфейс используется при вызове этого метода (будет ли он замораживать приложение)? Можно ли даже вызывать вашу реализацию в другом потоке?
Ответ 2
Попробуйте следующее:
class Foo2 : IFoo
{
public Task<Bar> CreateBarAsync()
{
return Task.FromResult<Bar>(SynchronousBarCreator());
}
}
Task.FromResult
создает уже выполненную задачу указанного типа, используя предоставленное значение.
Ответ 3
Если вы используете .NET 4.0, вы можете использовать TaskCompletionSource<T>
:
Task<Bar> CreateBarAsync()
{
var tcs = new TaskCompletionSource<Bar>();
tcs.SetResult(SynchronousBarCreator());
return tcs.Task
}
В конечном счете, если нет ничего асинхронного в отношении вашего метода, вы должны рассмотреть возможность разоблачения синхронной конечной точки (CreateBar
), которая создает новый Bar
. Таким образом, нет никаких сюрпризов и нет необходимости обертывать избыточным Task
.
Ответ 4
В дополнение к другим ответам, есть еще один вариант, который, я считаю, также работает с .NET 4.0:
class Foo2 : IFoo
{
public Task<Bar> CreateBarAsync()
{
var task = new Task<Bar>(() => SynchronousBarCreator());
task.RunSynchronously();
return task;
}
}
Примечание task.RunSynchronously()
. Это может быть самый медленный вариант, по сравнению с Task<>.FromResult
и TaskCompletionSource<>.SetResult
, но есть тонкая, но важная разница: поведение распространения ошибок.
Вышеупомянутый подход будет имитировать поведение метода async
, где исключение никогда не бросается в один и тот же стек стека (его разворачивание), а сохраняется в неактивном состоянии внутри объекта Task
. Вызывающий на самом деле должен наблюдать за ним через await task
или task.Result
, после чего он будет перезапущен.
Это не относится к Task<>.FromResult
и TaskCompletionSource<>.SetResult
, где любое исключение, созданное SynchronousBarCreator
, будет передаваться непосредственно вызывающему, разматывая стек вызовов.
У меня есть более подробное объяснение этого здесь:
Любая разница между "ждут Task.Run(); возвращение; " и" return Task.Run()"?
На стороне примечания я предлагаю добавить условие для отмены при разработке интерфейсов (даже если в настоящее время не используется/не отменяется):
interface IFoo
{
Task<Bar> CreateBarAsync(CancellationToken token);
}
class Foo2 : IFoo
{
public Task<Bar> CreateBarAsync(CancellationToken token)
{
var task = new Task<Bar>(() => SynchronousBarCreator(), token);
task.RunSynchronously();
return task;
}
}