Мой код действительно выполняется асинхронно? - PullRequest
0 голосов
/ 13 марта 2020

У меня есть несколько объектов в списке. Каждую минуту для каждой из них мне приходится загружать информацию API и сохранять полученные данные в базу данных.

Объекты в списке не зависят друг от друга. Метод для каждого объекта может быть вызван отдельно. В зависимости от времени отклика API, общее время выполнения для одного метода обычно составляет от ~ 0,5 до 5 секунд.

Следуя рекомендациям других потоков в stackoverflow, я создал следующий код:

private async void DownloadAndSaveDataForMyObjects(object state)
{
   try
   {
      await Task.Run(() => Parallel.ForEach(MyObjectsList, ServiceSingleObjectMethod));
   }
}


private void ServiceSingleObjectMethod(RehDeviceSmsRequest myObjectFromList)
{
   var apiInfo = GetInfoFromApi(myObjectFromList);
   SaveInfoToDatabase(myObjectFromList);
}

На мой взгляд, код асинхронный. Но ... У меня есть некоторые сомнения. Регистратор информирует меня о действиях, выполняемых во время работы приложения. Интервалы между вызовами методов для объектов довольно велики. Пожалуйста, посмотрите это:

[13:32:46 DBG][MyService]->MyMethod => Update object id: 54
[13:32:47 DBG][MyService]->MyMethod => Update object id: 9
[13:32:50 DBG][MyService]->MyMethod => Update object id: 47
[13:32:51 DBG][MyService]->MyMethod => Update object id: 21
[13:32:51 DBG][MyService]->MyMethod => Update object id: 53
[13:32:53 DBG][MyService]->MyMethod => Update object id: 37
[13:32:55 DBG][MyService]->MyMethod => Update object id: 62
[13:32:55 DBG][MyService]->MyMethod => Update object id: 63
[13:32:56 DBG][MyService]->MyMethod => Update object id: 64
[13:32:56 DBG][MyService]->MyMethod => Update object id: 55
[13:32:56 DBG][MyService]->MyMethod => Update object id: 36
[13:32:56 DBG][MyService]->MyMethod => Update object id: 30
[13:32:56 DBG][MyService]->MyMethod => Update object id: 46
[13:32:56 DBG][MyService]->MyMethod => Update object id: 26
[13:32:56 DBG][MyService]->MyMethod => Update object id: 56
[13:33:00 DBG][MyService]->MyMethod => Update object id: 29
[13:33:00 DBG][MyService]->MyMethod => Update object id: 57
[13:33:00 DBG][MyService]->MyMethod => Update object id: 42
[13:33:01 DBG][MyService]->MyMethod => Update object id: 38
[13:33:01 DBG][MyService]->MyMethod => Update object id: 32
[13:33:01 DBG][MyService]->MyMethod => Update object id: 48
[13:33:01 DBG][MyService]->MyMethod => Update object id: 40
[13:33:01 DBG][MyService]->MyMethod => Update object id: 31
[13:33:01 DBG][MyService]->MyMethod => Update object id: 49
[13:33:02 DBG][MyService]->MyMethod => Update object id: 58
[13:33:02 DBG][MyService]->MyMethod => Update object id: 9
[13:33:02 DBG][MyService]->MyMethod => Update object id: 33
[13:33:02 DBG][MyService]->MyMethod => Update object id: 41
[13:33:04 DBG][MyService]->MyMethod => Update object id: 50
[13:33:04 DBG][MyService]->MyMethod => Update object id: 34
[13:33:06 DBG][MyService]->MyMethod => Update object id: 17
[13:33:06 DBG][MyService]->MyMethod => Update object id: 59
[13:33:07 DBG][MyService]->MyMethod => Update object id: 45
[13:33:08 DBG][MyService]->MyMethod => Update object id: 61
[13:33:08 DBG][MyService]->MyMethod => Update object id: 54
[13:33:08 DBG][MyService]->MyMethod => Update object id: 43
[13:33:08 DBG][MyService]->MyMethod => Update object id: 51
[13:33:08 DBG][MyService]->MyMethod => Update object id: 52

Интервалы между обработкой последующих объектов довольно велики. Я думал, что мой асинхронный код сделает метод для каждого объекта, вызываемого одновременно (или очень, очень похожим) ...

Как я могу убедиться, что мой код асинхронный и работает правильно? Можете ли вы подтвердить, что я правильно вызываю задачи для каждого объекта?

1 Ответ

7 голосов
/ 13 марта 2020

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

Тем не менее, у вас есть большие концептуальные проблемы здесь. Вы используете Parallel.ForEach (для того, что выглядит) хлеб-и-масло IO-связанную работу , которая связывает / блокирует потоки, которые могут быть более подходящим образом выгружены в IO Completion ports , и, в свою очередь, имеет больше шансов для достижения более одновременных IO Bound рабочих нагрузок .

Короче говоря, Parallel.ForEach просто не подходит для рабочих нагрузок, связанных с вводом-выводом , он оптимизирован для рабочих нагрузок, связанных с ЦП и не поддерживает asyn c и ждите паттерна .

Таким образом, вы должны указать, что ваши методы IO будут asyn c (полностью вниз) и с использованием более подходящего технология, которая может работать с asyn c и шаблоном ожидания , например Task.WhenAll или TPL DataFlow ActionBlock<T> или Reactive Extensions . Кроме того, вам, вероятно, следует объединить вашу работу с базой данных , так что вы тоже не будете ее уничтожать. Это даст вам максимальную вероятность того, что ваши рабочие нагрузки не будут блокировать потоки пул потоков, и, в свою очередь, вы, скорее всего, найдете сквозное ( в общем) будет намного выше.

...