Метод, возвращающий задачу, которая ведет себя синхронно в консольном приложении C # - PullRequest
1 голос
/ 15 апреля 2019

В консольном приложении C # у меня есть класс репо с парой асинхронных методов:

public class SomeRepo
{
   internal Task<IList<Foo>> GetAllFooAsync() 
   { 
       // this is actually fake-async due to legacy code.
       var result = SomeSyncMethod();
       return Task.FromResult(result);
   }

   public Task<IList<Foo>> GetFilteredFooAsync()
   {             
       var allFoos = await GetAllFooAsync().ConfigureAwait(false);
       return allFoos.Where(x => x.IsFiltered);
   }
}

В Program.cs:

var someRepo = new SomeRepo();
var filteredFoos = someRepo.GetFilteredFooAsync(); // no await
// a couple of additional async calls (to other classes) without await..
// .. followed by:
await Task.WhenAll(filteredFoos, otherTask, anotherTask).ConfigureAwait(false);

Что меня сбивает с толку, так этоесли я установлю точку останова на 2-й строке в Program.cs, вызов someRepo.GetFilteredFooAsync() не переходит на следующую строку, а вместо этого останавливается до завершения операции (как если бы она была синхронной).Принимая во внимание, что если я изменю вызов на GetAllFooAsyncGetFilteredFooAsync), который будет заключен в Task.Run:

public class SomeRepo
{
   internal Task<IList<Foo>> GetAllFooAsync() { // ... }

   public Task<IList<Foo>> GetFilteredFooAsync()
   {             
       var allFoos = await Task.Run(() => GetAllFooAsync).ConfigureAwait(false);
       return allFoos.Where(x => x.IsFiltered);
   }
}

.., операция будет работать так, как ожидалось.Это потому, что GetAllFooAsync на самом деле является синхронным, но имитирует асинхронный рабочий процесс?

РЕДАКТИРОВАТЬ: переписал заголовок и добавил внутреннюю часть GetAllFooAsync, поскольку я понял, что они могут быть виновником проблемы.

Ответы [ 3 ]

2 голосов
/ 15 апреля 2019

Наличие ключевого слова async не делает метод асинхронным, он просто сигнализирует компилятору сделать преобразование кода метода в класс конечного автомата, который готов к использованию с асинхронными потоками. По сути, метод становится асинхронным, когда он выполняет асинхронную операцию, такую ​​как ввод-вывод, выгрузка работы в другой поток и т. Д., И в этом случае он состоит из 3 частей: синхронная часть, которая предшествует асинхронной операции, вызов асинхронной операции, которая инициирует операцию и возвращает управление вызывающему потоку и продолжение. В вашем случае последние две части отсутствуют, поэтому ваш вызов является синхронным.

2 голосов
/ 17 апреля 2019

У вас уже есть несколько хороших ответов, которые описывают , как async не делает вещи асинхронными, как async методы начинают выполняться синхронно, и как await может действовать синхронно, если его задача уже выполнена .

Итак, давайте поговорим о дизайне.

internal Task<IList<Foo>> GetAllFooAsync() 
{ 
  // this is actually fake-async due to legacy code.
  var result = SomeSyncMethod();
  return Task.FromResult(result);
}

Как уже отмечалось, это синхронный метод , но он имеет асинхронную сигнатуру. Это смущает; если метод не асинхронный, лучше использовать синхронный API:

internal IList<Foo> GetAllFoo()
{ 
  // this is actually fake-async due to legacy code.
  var result = SomeSyncMethod();
  return result;
}

и аналогичный метод, который его вызывает:

public IList<Foo> GetFilteredFoo()
{             
  var allFoos = GetAllFoo();
  return allFoos.Where(x => x.IsFiltered);
}

Так что теперь у нас есть синхронные реализации с синхронными API. Вопрос сейчас о потреблении. Если вы используете это из ASP.NET, я рекомендую использовать их синхронно. Однако, если вы используете их из приложения с графическим интерфейсом, вы можете использовать Task.Run, чтобы перенести синхронную работу в поток пула потоков, а затем обработать ее, как если бы она была асинхронной:

var someRepo = new SomeRepo();
var filteredFoos = Task.Run(() => someRepo.GetFilteredFoo()); // no await
// a couple of additional async calls (to other classes) without await..
// .. followed by:
await Task.WhenAll(filteredFoos, otherTask, anotherTask).ConfigureAwait(false);

В частности, я не рекомендую использовать Task.Run для реализации, например, GetAllFooAsync. Вы должны использовать Task.Run для вызова методов, а не для их реализации , а Task.Run в основном следует использовать для вызова синхронного кода из потока GUI (т. Е. Не в ASP.NET).

2 голосов
/ 15 апреля 2019

Это потому, что GetAllFooAsync на самом деле является синхронным, но имитирует асинхронный рабочий процесс?

Да, Task.FromResult возвращает задачу, которая немедленно RanToCompletion, поэтому она является синхронной. Люди часто забывают, что задачи могут в некоторых случаях уже быть выполнены, когда они возвращаются, и поэтому не выполняются асинхронно.

Этот метод создает объект Task, у которого свойство Task.Result является результатом, а свойство Status - RanToCompletion. Этот метод обычно используется, когда возвращаемое значение задачи сразу известно без выполнения более длинного пути кода. Пример предоставляет иллюстрацию.

...