2

Допустим, есть коллекция значений и какой-то асинхронный метод.

Если я сделаю вот так:

Task.WhenAll(SomeList.Select(x=>AsyncMethod(x))) 

То будет ли организована какая-нибудь параллельная работа автоматически или все это целиком зависит от того, как устроен асинхронный метод сам по себе или все таки в каких-то случаях параллельная работа будет?

iluxa1810
  • 24,899
  • А где await перед Task? И, кажется, IDE в таком случае сам предлагает async версию лямбды писать. Но могу и путать. – CrazyElf Dec 15 '20 at 13:09
  • А зачем await? Я получаю коллекцию задач, которую потом всю дожидаюсь. Если делать async, то будет Task<Task<[]>>. – iluxa1810 Dec 15 '20 at 13:15
  • 1
    Асинхронность и параллельность по сути это разные вещи. Вот к примеру JavaScript однопоточный, при этом можно писать асинхронный код – Aziz Umarov Dec 15 '20 at 13:15
  • 1
    А чтобы было параллельно нужно организовывать работу потоков для такой работы. Так что написав даже кучу Task.WhenAll ничего не гарантируется. Потоки тупо могут ожидать друг друга – Aziz Umarov Dec 15 '20 at 13:17
  • @iluxa1810 Да, пожалуй в вашем случае это не нужно. У меня просто были более сложные конструкции после select, там нужно уже было результат работы функций по полям распихивать – CrazyElf Dec 15 '20 at 13:18
  • Наверное, если AsyncMethod внутри себя будет делать await, то другие таски смогут получить управление, пока await ждёт, а так да, параллельность тут не гарантирована. – CrazyElf Dec 15 '20 at 13:19
  • Пример блокирующей записи в файл. Доступ для записи может имееть только один поток в одно и тоже время. И плевать как вы хотите расспаралелить потоки доступ будет по очереди – Aziz Umarov Dec 15 '20 at 13:21
  • Нормально отработает. Если асинхронный метод - на самом деле асинхронный, то никаких проблем здесь не возникнет. Этот паттерн в одном из блогов сам Stephen Toub показывал. – aepot Dec 15 '20 at 13:38
  • @aepot Вот пример количество потоков для процесса один. Методы асинхронные, можно ли считать что что-либо будет работать параллельно в данном процессе? – Aziz Umarov Dec 15 '20 at 14:01
  • @AzizUmarov если вручную запилен или настроен TaskScheduler, то разработчик наверное должен наверняка знать, как именно у него отработает вся эта асинхронная история, а не задавать вопросы типа того что выше. Поэтому предположу, что там всё дефолтное. – aepot Dec 15 '20 at 14:03
  • @aepot Даже если все дефолтное. Память у них у всех общая, соответственно возникают попутные проблемы в многопоточной среде. И паралельность чтобы была глядя только на AsyncMethod(x) это надо быть волшебником – Aziz Umarov Dec 15 '20 at 14:06
  • и То что оно отработает в этом нет проблем. Вопрос был о параллельности – Aziz Umarov Dec 15 '20 at 14:07
  • А распараллеливание и вовсе не простая задача как кажется на первый взгляд. – Aziz Umarov Dec 15 '20 at 14:08
  • 1
    @AzizUmarov тогда ответ такой: асинхронность никак не влияет на параллельность и наоборот. Ну потому что потому. :) – aepot Dec 15 '20 at 14:10

3 Answers3

10

Нет, конечно.

Async/await вовсе ничего не знает о потоках, Task — всего лишь абстракция задания, которое каким-то образом выполняется.

Если ваши Task'и ожидают внутренние подзадания через await (и внутренние подзадания тоже all the way down), и не содержат медленного кода (например, длинных синхронных вычислений), обычно неважно, в каком потоке они выполняются, т. к. реальная работа разных заданий не будет блокировать друг друга.

Если же у вас есть синхронный тяжёлый код, нужно явно «увести» его из потока (например, при помощи Task.Run) внутри AsyncMethod. (Если AsyncMethod так не делает из каких-то соображений, напишите вместо него лямбду-обёртку.)

VladD
  • 206,799
3

Вариант 1 - отсутствие чего либо асинхронного

async Task Main()
{
    var tasks = Enumerable.Range(0, 10).Select(x=>AsyncMethod()).ToList();
Console.WriteLine($&quot;main in thread {Thread.CurrentThread.ManagedThreadId}&quot;);
await Task.WhenAll(tasks);
Console.WriteLine($&quot;main in thread {Thread.CurrentThread.ManagedThreadId}&quot;);

}

async Task AsyncMethod() { Console.WriteLine($"started in thread {Thread.CurrentThread.ManagedThreadId}"); var ret = await Task.FromResult(10); Console.WriteLine($"finished in thread {Thread.CurrentThread.ManagedThreadId}");
}

В выводе все потоки одинаковые

started in thread 12
finished in thread 12
started in thread 12
finished in thread 12
started in thread 12
finished in thread 12
started in thread 12
finished in thread 12
started in thread 12
finished in thread 12
started in thread 12
finished in thread 12
started in thread 12
finished in thread 12
started in thread 12
finished in thread 12
started in thread 12
finished in thread 12
started in thread 12
finished in thread 12
main in thread 12
main in thread 12

Вариант 2 - какая то асинхронная задача

async Task Main()
{
    var tasks = Enumerable.Range(0, 10).Select(x=>AsyncMethod()).ToList();
Console.WriteLine($&quot;main in thread {Thread.CurrentThread.ManagedThreadId}&quot;);
await Task.WhenAll(tasks);
Console.WriteLine($&quot;main in thread {Thread.CurrentThread.ManagedThreadId}&quot;);

}

async Task AsyncMethod() { Console.WriteLine($"started in thread {Thread.CurrentThread.ManagedThreadId}"); await Task.Delay(1000); Console.WriteLine($"finished in thread {Thread.CurrentThread.ManagedThreadId}");
}

В выводе выполнение прыгает по потокам, можно думать о какой то многопоточности

started in thread 12
started in thread 12
started in thread 12
started in thread 12
started in thread 12
started in thread 12
started in thread 12
started in thread 12
started in thread 12
started in thread 12
main in thread 12
finished in thread 14
finished in thread 8
finished in thread 11
finished in thread 11
finished in thread 11
finished in thread 11
finished in thread 8
finished in thread 9
finished in thread 14
finished in thread 10
main in thread 10

Вариант 3 - какая то асинхронная задача + контекст синхронизации WPF

async Task Main()
{
    SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext());
var tasks = Enumerable.Range(0, 10).Select(x=&gt;AsyncMethod()).ToList();

Console.WriteLine($&quot;main in thread {Thread.CurrentThread.ManagedThreadId}&quot;);
await Task.WhenAll(tasks);
Console.WriteLine($&quot;main in thread {Thread.CurrentThread.ManagedThreadId}&quot;);

}

async Task AsyncMethod() { Console.WriteLine($"started in thread {Thread.CurrentThread.ManagedThreadId}"); await Task.Delay(1000); Console.WriteLine($"finished in thread {Thread.CurrentThread.ManagedThreadId}");
}

И мы снова в одном потоке

started in thread 12
started in thread 12
started in thread 12
started in thread 12
started in thread 12
started in thread 12
started in thread 12
started in thread 12
started in thread 12
started in thread 12
main in thread 12
finished in thread 12
finished in thread 12
finished in thread 12
finished in thread 12
finished in thread 12
finished in thread 12
finished in thread 12
finished in thread 12
finished in thread 12
finished in thread 12
main in thread 12

Вариант 3 - какая то асинхронная задача + контекст синхронизации WPF + ConfigureAwait(false)

async Task Main()
{
    SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext());
var tasks = Enumerable.Range(0, 10).Select(x=&gt;AsyncMethod()).ToList();

Console.WriteLine($&quot;main in thread {Thread.CurrentThread.ManagedThreadId}&quot;);
await Task.WhenAll(tasks);
Console.WriteLine($&quot;main in thread {Thread.CurrentThread.ManagedThreadId}&quot;);

}

async Task AsyncMethod() { Console.WriteLine($"started in thread {Thread.CurrentThread.ManagedThreadId}"); await Task.Delay(1000).ConfigureAwait(false); Console.WriteLine($"finished in thread {Thread.CurrentThread.ManagedThreadId}");
}

И снова несколько потоков

started in thread 12
started in thread 12
started in thread 12
started in thread 12
started in thread 12
started in thread 12
started in thread 12
started in thread 12
started in thread 12
started in thread 12
main in thread 12
finished in thread 6
finished in thread 18
finished in thread 6
finished in thread 6
finished in thread 6
finished in thread 6
finished in thread 6
finished in thread 6
finished in thread 14
finished in thread 18
main in thread 12

Таким образом, наличие асинхронного метода не говорит ничего о том, будет ли асинхронный вызов вообще и будет ли использована многопоточность.

aepot
  • 49,560
tym32167
  • 32,857
1

Добавлю ещё немного слов к ответам выше. Даже если мы будем говорить о многопоточности -это метод, который выделяет отдельные потоки выполнения; они по существу являются легкими процессами с некоторыми преимуществами по отношению к общим ресурсам от их родителя. А параллелизм - способ выполнения процессов/потоков. Они либо последовательны (следуют друг за другом), либо параллельны (происходят одновременно).

Даже имея многопоточность вы ничего не можете говорить о параллельности.

Aziz Umarov
  • 22,567
  • 2
  • 10
  • 33