5

Недавно прочитал статью на хабре (upd: из комментов понял, что нужно прицепить цитату, по которой далее вопрос)

Как только код доходит до метода Task.Run(), достаётся другой поток из пула потоков и в нём исполняется код, который мы передали в Task.Run(). Старый поток, как и положено приличному потоку, возвращается в пул и ждёт, когда его снова позовут делать работу. Новый поток выполняет переданный код, доходит до синхронной операции, синхронно выполняет её (ждёт пока операция не будет выполнена) и идёт дальше по коду. Иными словами, операция так и осталась синхронной: мы, как и раньше, используем поток во время выполнения синхронной операции. Единственное отличие — мы потратили время на переключение контекста при вызове Task.Run() и при возврате в ExecuteOperation(). Всё стало немножечко хуже.

Один из вопросов, который там рассматривается: вызов Task.Run - это антипаттерн, и нужен он только для отзывчивости GUI.

Вопрос именно про Task.Run(() => _anyWork()), где _anyWork() содержит синхронный код. То, что написано в статье, звучит достаточно логично, если делать так:

await DoWork();
...

Task DoWork() => Task.Run(_work);

Да, в таком случае создается лишняя нагрузка на пул потоков. Но, ведь если делать так:

var task1 = DoWork1();
var task2 = DoWork2();
var task3 = DoWork3();
await Task.WhenAll(task1, task2, task3);

Task.Run сразу превращается в нормальный код, ведь так?

Поток, который будет выполнять этот код, создаст три других потока (upd: оговорился: инициирует добавление работы в очередь, которая будет запущена в ThreadPool), которые параллельно будут выполнять свою работу параллельно. Дальше, когда он встретит await - он вернет управление (в итоге, скорее всего, вернется в пул). Исправьте, пожалуйста, если не так.

Если это так - возникает вопрос: где проходит эта грань, между плохой реализацией, и нормальной? В небиблиотечном коде понятно - если вызывается метод, а ожидание где-то дальше - то можно делать Task.Run. В библиотечном же - с одной стороны, мы можем распараллелить работу своих методов, если клиент будет ожидать их после вызова. С другой - мы можем зря увеличить нагрузку, если клиент будет ожидать результат сразу при вызове. Знать точно, как будет вызывать методы клиент - мы не можем, можем только дать рекомендации в документации.

Возможно есть какие-то официальные рекомендации MS? На msdn я нашел только сухое описание работы методов.

A1essandro
  • 2,938
  • Вы можете сконденсировать мысль, выраженную в статье до MVCE? Чтобы не требовалось ее читать для понимания Вашего вопроса – Alex Yu Feb 21 '19 at 09:01
  • Поток, который будет выполнять этот код, создаст три других потока - вообще говоря, это не так – yolosora Feb 21 '19 at 09:09
  • 1
  • это Task.Run не создает новый поток, а берет готовый поток из пула
  • – tym32167 Feb 21 '19 at 09:32
  • 1
  • Task.Run сразу превращается в нормальный код, ведь так? что значит нормальный код? Критении нормальности кода?
  • – tym32167 Feb 21 '19 at 09:37
  • 1
  • создается лишняя нагрузка на пул потоков пул потоков существует для того, чтоюы его нагружали. Что значит лишняя нагрузка?
  • – tym32167 Feb 21 '19 at 09:38