Разница между TPL и async / await (обработка потоков)

Пытаемся понять разницу между TPL & _1 _ / _ 2_, когда дело доходит до создания потоков.

Я считаю, что TPL (TaskFactory.StartNew) работает аналогично ThreadPool.QueueUserWorkItem в том, что он ставит в очередь работу над потоком в пуле потоков. Конечно, если вы не используете TaskCreationOptions.LongRunning, который создает новый поток.

Я думал, что _6 _ / _ 7_ будет работать так же по сути:

ОСАГО:

Factory.StartNew( () => DoSomeAsyncWork() )
.ContinueWith( 
    (antecedent) => {
        DoSomeWorkAfter(); 
    },TaskScheduler.FromCurrentSynchronizationContext());

Async/Await:

await DoSomeAsyncWork();  
DoSomeWorkAfter();

будет идентично. Из того, что я читал, кажется, что _12 _ / _ 13_ только «иногда» создает новый поток. Итак, когда он создает новый поток, а когда не создает новый поток? Если бы вы имели дело с портами завершения ввода-вывода, я мог бы видеть, что ему не нужно создавать новый поток, но в противном случае я бы подумал, что это придется. Думаю, мое понимание FromCurrentSynchronizationContext тоже всегда было немного нечетким. Я всегда думал, что это, по сути, поток пользовательского интерфейса.


person coding4fun    schedule 23.04.2012    source источник
comment
На самом деле TaskCreationOptions.LongRunning не гарантирует создание нового потока. Согласно MSDN, опция LongRunning дает только подсказку для планировщика; это не гарантирует выделенный поток. Я выяснил это на собственном горьком опыте.   -  person eduncan911    schedule 03.11.2012
comment
@ eduncan911 хотя то, что вы говорите о документации, верно, я некоторое время назад просмотрел исходный код TPL и почти уверен, что на самом деле новый выделенный поток всегда создается, когда указано TaskCreationOptions.LongRunning.   -  person Zaid Masud    schedule 27.02.2013
comment
@ZaidMasud: Вы можете еще раз взглянуть. Я знаю, что он объединял потоки, потому что Thread.CurrentThread.IsThreadPoolThread возвращал истину для коротких потоков в несколько сотен миллисекунд. не говоря уже о переменных ThreadStatic, которые я использовал для вывода на несколько потоков, что приводило к разного рода хаосу. Мне пришлось заставить свой код обновлять несколько Thread () по старинке, чтобы гарантировать выделенный поток. Другими словами, я не мог использовать TaskFactory для выделенных потоков. При желании вы можете реализовать свой собственный TaskScheduler, который всегда возвращает выделенный поток.   -  person eduncan911    schedule 11.03.2013


Ответы (2)


Я считаю, что TPL (TaskFactory.Startnew) работает аналогично ThreadPool.QueueUserWorkItem в том, что он ставит в очередь работу над потоком в пуле потоков.

Практически.

Из того, что я читал, кажется, что async / await только «иногда» создает новый поток.

На самом деле, этого никогда не происходит. Если вам нужна многопоточность, вы должны реализовать это самостоятельно. Есть новый метод Task.Run, который является сокращением для Task.Factory.StartNew, и, вероятно, это наиболее распространенный способ запуска задачи в пуле потоков.

Если бы вы имели дело с портами завершения ввода-вывода, я мог бы видеть, что ему не нужно создавать новый поток, но в противном случае я бы подумал, что это придется.

Бинго. Таким образом, такие методы, как Stream.ReadAsync, фактически создают Task оболочку вокруг IOCP (если Stream имеет IOCP).

Вы также можете создавать некоторые «задачи», не связанные с вводом-выводом и не связанные с процессором. Простым примером является Task.Delay, который возвращает задачу, которая завершается через некоторый период времени.

В _7 _ / _ 8_ замечательно то, что вы можете поставить в очередь некоторую работу в пуле потоков (например, Task.Run), выполнить некоторые операции, связанные с вводом-выводом (например, Stream.ReadAsync), и выполнить некоторые другие операции (например, Task.Delay). ... а они все задачи! Их можно дождаться или использовать в комбинациях вроде Task.WhenAll.

Любой метод, который возвращает Task, может быть awaited - он не обязательно должен быть async методом. Таким образом, Task.Delay и операции, связанные с вводом-выводом, просто используют TaskCompletionSource для создания и завершения задачи - единственное, что делается в пуле потоков, - это фактическое завершение задачи при возникновении события (тайм-аут, завершение ввода-вывода и т. Д.).

Я предполагаю, что мое понимание FromCurrentSynchronizationContext всегда было немного нечетким. Я всегда думал, что это, по сути, поток пользовательского интерфейса.

Я написал статью о SynchronizationContext. В большинстве случаев SynchronizationContext.Current:

  • - это контекст пользовательского интерфейса, если текущий поток является потоком пользовательского интерфейса.
  • - это контекст запроса ASP.NET, если текущий поток обслуживает запрос ASP.NET.
  • в противном случае - это контекст пула потоков.

Любой поток может установить свой собственный SynchronizationContext, поэтому есть исключения из правил выше.

Обратите внимание, что ожидающий Task по умолчанию будет планировать оставшуюся часть метода async для текущего SynchronizationContext , если он не равен нулю; иначе идет текущий TaskScheduler. Сегодня это не так важно, но в ближайшем будущем станет важным отличием.

Я написал свое собственное _25 _ / _ 26_ intro в моем блоге, и Стивен Туб недавно опубликовал отличный _27 _ / _ 28_ FAQ.

Что касается «параллелизма» и «многопоточности», см. этот связанный вопрос SO . Я бы сказал, что async разрешает параллелизм, который может быть, а может и не быть многопоточным. Легко использовать await Task.WhenAll или await Task.WhenAny для параллельной обработки, и если вы явно не используете пул потоков (например, Task.Run или ConfigureAwait(false)), вы можете одновременно выполнять несколько параллельных операций (например, несколько операций ввода-вывода или другие типа Delay) - и для них не нужен поток. Я использую термин «однопоточный параллелизм» для такого рода сценариев, хотя на хосте ASP.NET вы можете фактически получить «нулевой -поточный параллелизм». Что довольно мило.

person Stephen Cleary    schedule 23.04.2012
comment
Первая ссылка мертва. - person Felipe Deveza; 10.12.2017
comment
ссылка на async / await FAQ не работает - person Artemious; 12.08.2020
comment
Ага. Microsoft переместила кучу вещей и сломала практически все ссылки на их контент. Этот ответ принимает PR. - person Stephen Cleary; 12.08.2020

async / await в основном упрощает ContinueWith методы (продолжения в стиле передачи продолжения)

Он не вводит параллелизм - вам все равно придется делать это самостоятельно (или использовать асинхронную версию метода фреймворка).

Итак, версия C # 5 будет:

await Task.Run( () => DoSomeAsyncWork() );
DoSomeWorkAfter();
person Nick Butler    schedule 23.04.2012
comment
так где же он запускает DoSomeAsyncWork (версия async / wait) в моем примере выше? Если он работает в потоке пользовательского интерфейса, как он не блокируется? - person coding4fun; 23.04.2012
comment
Ваш пример ожидания не будет компилироваться, если DoSomeWorkAsync() вернет void или что-то, чего нельзя ожидать. В вашем первом примере я предположил, что это последовательный метод, который вы хотите запустить в другом потоке. Если вы измените его так, чтобы он возвращал Task, без введения параллелизма, тогда да, он будет заблокирован. В том смысле, что он будет выполняться последовательно и быть таким же, как обычный код в потоке пользовательского интерфейса. await уступает только в том случае, если метод возвращает ожидаемый объект, который еще не завершен. - person Nick Butler; 23.04.2012
comment
ну, я бы не сказал, что он бежит, куда хочет. Вы использовали Task.Run для выполнения кода в DoSomeAsyncWork, поэтому в этом случае ваша работа будет выполняться в потоке пула потоков. - person Michael Ray Lovett; 09.11.2012
comment
Мне понравилась лаконичность вашего ответа. - person RBT; 05.03.2020