2

С практическим асинхронным программированием столкнулся относительно недавно, и, изучая тему детальнее, назрел вопрос:

При создании задачи с помощью Task.Factory.StartNew (с параметром TaskCreationOptions.LongRunning) поток для задачи выбирается НЕ из пула.
Какой поток выбирается при создании с помощью TaskCompletionSource - из пула или не из пула?


P.S. Читал, мол, эти два способа эквивалентны... Но хочется разъяснтить очень важную деталь: какой поток выбирается при втором способе?
sp7
  • 5,269

2 Answers2

5

Никакой.

Task — это не обязательно «код, бегущий в каком-то потоке». Это не абстракция над методом, бегущем непонятно где. Это лишь формальное обещание когда-либо предоставить результат. Работает ли над этим результатом один поток, несколько, или вообще ни одного — это внутренняя подробность, скрытая внутри Task'а, и недоступная наблюдателю.

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

Например, вы можете создать TaskCompletionSource, запустить таймер, и по приходу таймера завершить задачу. При этом, понятно, ваш таск не будет бежать ни в каком потоке.


Дополнительное чтение по теме: Нет никакого потока (Stephen Cleary, перевод Андрея Часовских).

VladD
  • 206,799
  • Так ведь когда мы создаём Task, то эта задача будет выполнена в каком (в каких) либо потоке из пула. А как будет с TaskCompletionSource? Так же, но с "фишками" этого класса по поводу блокировок? –  Aug 06 '17 at 18:57
  • @khirnick: Обновил ответ, читайте. – VladD Aug 06 '17 at 19:00
  • спасибо за ответ. Назрел вопрос: если мы создадим TaskCompletionSource и на SetResult поставим "длительную" функцию, то в каком потоке будет выполняться этот SetResult? –  Aug 06 '17 at 19:08
  • 3
    @khirnick: А что означает «на SetResult поставим "длительную" функцию»? Что значит «поставить функцию»? – VladD Aug 06 '17 at 20:14
  • @khirnick все методы TaskCompletionSource (и их продолжения) выполняются в текущем потоке, потому что это просто "обертка" над таском, никакого отношения к потокам не имеющая. Она нужна, например, там, где нужно выставить "новый" асинхронный АПИ при наличии "старого" (см. ответ sp7). – andreycha Aug 07 '17 at 09:52
  • @andreycha понял. А если SetResult будет внутри таймера, то SetResult будет выполняться в потоке таймера? (про таймеры знаю, что они используют пул потоков) –  Aug 07 '17 at 11:33
  • @khirnick: Не все таймеры используют пул потоков, а некоторые используют, но не всегда: https://ru.stackoverflow.com/a/696486/10105 – VladD Aug 07 '17 at 11:39
  • @VladD ого, спасибо больше. Очень интересно и подробно! –  Aug 07 '17 at 11:50
  • @khirnick: Пожалуйста! Рад, что пригодилось! – VladD Aug 07 '17 at 11:52
1

Класс TaskCompletionSource<T> - это класс позволяющий создать задачу Task, которой вы управляете как марионеткой. Ее можно в любой момент сделать успешно завершившейся или записать в нее исключение и тем самым сказав, что она завершилась с ошибкой. Данный класс можно применять, например, при переводе с паттерна IAsyncResult на TAP.

 public static Task<IPHostEntry> GetHostEntryAsync(string hostNameOrAddress)
 {
     var tcs = new TaskCompletionSource<IPHostEntry>();
     Dns.BeginGetHostEntry(hostNameOrAddress, asyncResult =>
     {
         try
         {
             IPHostEntry result = Dns.EndGetHostEntry(asyncResult);
             tcs.SetResult(result);
         }
         catch(Exception e)
         {
             tcs.SetException(e);
         }
    }, null);

    return tcs.Task;
}
sp7
  • 5,269