Подтвердить что ты не робот

Как реализовать метод интерфейса, который возвращает 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 в приведенном выше примере в надежде сделать основной вопрос более понятным.

4b9b3361

Ответ 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;
    }
}