Re[58]: Зачем Майкрософту рубить сук, на котором он сидит?
От: Ночной Смотрящий Россия  
Дата: 17.05.14 15:08
Оценка:
Здравствуйте, Ikemefula, Вы писали:

I>покажи, как же таска владеет потоком монопольно.


Я тебе уже наглядно продемонстрировал это. И, на всякий случай, никакого эвентлупа там нет, это консольное приложение.
Добавить туда еще идентификатор вызывающего потока и IOCP? Легко:
class Program
{
    private static async Task<int> MyTask()
    {
        var wc = new WebClient();
        var data = await wc.DownloadDataTaskAsync("http://rsdn.ru/forum/flame.comp/5089377.all");
        return Thread.CurrentThread.ManagedThreadId;
    }

    private async static void Do()
    {
        Console.WriteLine("cur = " + Thread.CurrentThread.ManagedThreadId);
        var id1 = await MyTask();
        var id2 = await MyTask();
        Console.WriteLine("id1 = " + id1);
        Console.WriteLine("id2 = " + id2);
    }

    static void Main()
    {
        Do();
        Console.Read();
    }
}


Вывод:
cur = 10
id1 = 19
id2 = 16

Никаких подозрений не появилось?

НС>>Стало еще меньше понятно о чем ты хотел сказать.

I>Неудивительно. await, yield — это прямое указание прерывания текущей задачи

Спасибо, КО. Только при чем тут таски? Это те самые продолжения, о которых я которое уже письмо твержу. Которые могут быть в том числе и при помощи зеленых потоков реализованы. И которые зелеными потоками не являются, и в современном шарпе реализованы тоже без них. Более того, async/await (и тем более yield) вполне можно использовать совсем без TPL, компилятор проверяет только символьное соответствие имен классов и методов.

I>Скажем, как то иначе прикрутить к потоку IOCP или таймер будет сложновато, придется изобрести эвент-луп.


Что значит "прикрутить к потоку IOCP или таймер" (системный таймер, кстати, тоже через IOCP обычно используют)? И как IOCP в тасках работает в консольном или веб приложении, где никаких эвентлупов нет?
Re[58]: Зачем Майкрософту рубить сук, на котором он сидит?
От: Ночной Смотрящий Россия  
Дата: 17.05.14 15:25
Оценка:
Здравствуйте, Ikemefula, Вы писали:

НС>>Ты по прежнему путаешь таски и реализацию continuation monad в компиляторе C#.

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

Верно. У тебя логическая ошибка — из то что продолжения можно реализовать при помощи кооперативной многозадачности (и даже, для особых извращенцев, наоборот на продолжениях кооперативную многозадачность, запихнув обработку продолжений в цикл, не обязательно цикл выборки сообщений) вовсе не следует что продолжения являются кооперативной многозадачностью. Более того, по факту и для CPU bound и для IO bound задач никакой кооперативной многозадачности нет, используются механизмы ОС напрямую с многозадачностью вытесняющей. Если, конечно, не подсунуть контекст, запускающий таски в одном потоке (asp.net, к примеру, сейчас для cpu bound так делает. Впрочем, даже и в этом случае можно игнорировать контекст вызовом ConfigureAwait(false), но это так, лирика).

I>То есть, в кратце, тебе надо пояснить, как будет работать в тасках асинхронный код навроде IOCP или таймера того же.


Не понимаю вопроса. Он будет работать ровно так же, как и без тасков. Таски в случае IOCP, как я уже писал, нужны только для интеграции в общий граф зависимостей, непосредственно управление IOCP целиком на совести ОС.
Re[58]: Зачем Майкрософту рубить сук, на котором он сидит?
От: Ночной Смотрящий Россия  
Дата: 17.05.14 15:26
Оценка:
Здравствуйте, Ikemefula, Вы писали:

I>Кстати говоря, await можно перепилить на колбеки и всунуть это в таски.


Чего, простите?

I> Интересно, как ты в этом случае будешь выкручиваться.


А почему я должен выкручиваться?
Re[63]: Зачем Майкрософту рубить сук, на котором он сидит?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 17.05.14 15:28
Оценка:
Здравствуйте, Ночной Смотрящий, Вы писали:

N>>IOCP тут, действительно, ни при чём. А вот называть sleep(), который превращается в перевод треда в спящее состояние и ожидание прерывания от таймера (через прокладку в виде диспетчера), "CPU bound", это верх запутывания.

НС>С точки зрения тасков это CPU bound.

"С точки зрения тасков" это вообще долгая задача. Почему она долгая — им уже не важно. Важно, что она выходит за пределы рекомендуемой применимости (хотя бы потому, что такими задачами можно забить весь пул и парализовать его).

НС> Таски это не низкоуровневый механизм ядра, это прикладная библиотека высокого уровня. Зачем ты пытаешься навесить на нее абсолютно несвойственный ей функционал — мне непонятно.


Я ничего такого не пытаюсь, не надо мне приписывать. Я показываю, что это не их назначение.

N>> Это чистое IO, и эффект от него такой же, как если бы задача сделала блокирующий read().

НС>Ну так не надо делать блокирующий read.

О! Что и требовалось показать. А в случае что честных, что зелёных веток (в том виде, как я их знаю) с этим проблем нет — блокирующий вызов будет сигналом к переключению.

НС>>>>>Не знаю что у тебя там возникает, но таски прекрасно справляются без кооперативной многозадачности.

N>>эта реплика становится неадекватной, потому что "таски" не могут справляться без кооперативной задачности любого вида с ситуацией заказа многих ожиданий.
НС>Никто и не обещал что они будут справляться вообще со всем (кроме, разве, Икемефулы). Они решают вполне определенный, пусть и довольно широкий круг задач, и решают его хорошо. И обходятся при этом без зеленых потоков.

Он этого не обещал, по крайней мере в этом треде. Ты как-то очень странно читаешь.

НС>>> не стоит путать таски с continuation monad в шарпе.

N>>Я не путаю, потому что последнего просто не знаю Ну нету у меня шарпа в работе.
НС>Это на случай если ты принимаешь на веру то, что тут пишет Икемефула. Потому что на самом деле есть 2 сущности:
НС>1) TPL ака таски. Это библиотека, которая появилась задолго до шарповских async/await, в 4 фреймворке. Цель ее создания — облегчение написания асинхронного fine grained кода. Первая версия создавалась прежде всего для внутреннего слоя PLINQ — декларативного использования асинхронности на задачах обработки последовательностей. Потом таски стали использовать в Reactive Extensions, потом в ASP.NET MVC. Это все еще до C# 5, который с поддержкой асинхронности.
НС>2) async/await в C# 5+. Это вшитая в язык continuation monad. Если очень грубо, то компилятор разрезает линейный как бы синхронный исходный код по явно указанным точкам на куски, выстраивая зависимости между ними. А затем, на основании этой информации, создает FSM, который позволяет выполнять эти куски по отдельности (логика примерно та же, что и при реализации yield для итераторов). Соответственно, визуально это выглядит как будто кто то "прерывает" выполнение синхронного кода, но на самом деле никакого синхронного непрерывного кода при этом нет. Естественно, что обработать таким образом компилятор может только то что в исходниках, а не системные вызовы типа Thread.Sleep.

Это, действительно, слишком грубое объяснение. Потому что не покрывает даже вход в асинхронный контекст — вызов async функции из синхронного кода. Если компилятор не опознаёт, что здесь идёт вызов именно async функции, то вся эта кусочно-гнездовая развесистость может быть только в отдельных нитях, а если опознаёт (например, по типу возврата Task) — то нужно ли было вводить слово async для таких методов?
Далее, я не вижу, чтобы описывался какой-то интеллект по выходу за пределы этого в случае внешних работ; по описанию в MSDN их надо явно переваливать на всякие Task.Run(). То есть получается какой-то "домен" асинхронного выполнения, который не имеет чёткого синтаксического отделения от основного. В первую очередь это проблема с библиотеками. Могут ли библиотечные функции блокироваться в таком случае? Если нет, то как им это гарантированно запретить и что делать с такими попытками?
А есть ли средство аккуратной привязки контекста к месту исполнения (такого, как TLS в тредах), или надо тащить все параметры за собой?
А как писать своими средствами все эти ReadFileAsync()?
Извини, если некоторые вопросы покажутся глупыми, но описание в MSDN написано слишком изиотически, понять по нему детали реализации практически невозможно.

НС>И да, здесь определенная связь с зелеными потоками есть — в других языках, о которых Икемефула упоминал, для реализации продолжений в основном используют зеленые потоки и реализуют эти самые продолжжения в лоб. Хуже того, лет 10 назад МС даже выкладывал в качестве демонстрации расширяемости CLR Host реализацию продолжений (yield) на системных файберах для обычного древнего C# 1.0.


Почему так сразу и "хуже"?

НС>Я же тут пытаюсь донести мысль, что то что зеленые потоки могут быть использованы для реализации async/await, не делает async/await зелеными потоками.


Ну, кажется, ты споришь с кем-то, кого в этом треде вообще не было.
The God is real, unless declared integer.
Re[64]: Зачем Майкрософту рубить сук, на котором он сидит?
От: Ночной Смотрящий Россия  
Дата: 17.05.14 16:07
Оценка:
Здравствуйте, netch80, Вы писали:

N>"С точки зрения тасков" это вообще долгая задача.


Шо, с любым значением параметра?

N>Важно, что она выходит за пределы рекомендуемой применимости (хотя бы потому, что такими задачами можно забить весь пул и парализовать его).


Ну и? Что тебя удивляет? Я ж говорю, при желании TPL вполне можно поставить раком.

НС>> Таски это не низкоуровневый механизм ядра, это прикладная библиотека высокого уровня. Зачем ты пытаешься навесить на нее абсолютно несвойственный ей функционал — мне непонятно.

N>Я ничего такого не пытаюсь, не надо мне приписывать. Я показываю, что это не их назначение.

Что не их назначение? Прерывать вызов Thread.Sleep? А что, кто то утверждал обратное?

N>>> Это чистое IO, и эффект от него такой же, как если бы задача сделала блокирующий read().

НС>>Ну так не надо делать блокирующий read.
N>О! Что и требовалось показать.

Зачем тебе потребовалось эту очевидность показать?

N> А в случае что честных, что зелёных веток (в том виде, как я их знаю) с этим проблем нет — блокирующий вызов будет сигналом к переключению.


Честные ветки никуда не делись. А зеленые на Thread.Sleep прореагируют точно так же, как и TPL. Поэтому там есть своя альтернатива, возвращающая управление в менеджер. И вообще таски с этой задачей никак не связаны, они ее решать и не пытаются. Ее решают продолжения.
Еще раз, по буквам, уж не знаю как еще объяснить — таски вообще никак не связаны с кооперативной многозадачностью, абсолютно. Они обеспечивают эффективную реализацию fine grained параллелизма с удобным API, и все. А все игры с await — совсем другая механика.

N>Это, действительно, слишком грубое объяснение. Потому что не покрывает даже вход в асинхронный контекст — вызов async функции из синхронного кода.


А почему оно должно его покрывать? Это личное дело контекста. И контекст, в том числе, может вообще ничего не делать.

N> Если компилятор не опознаёт, что здесь идёт вызов именно async функции, то вся эта кусочно-гнездовая развесистость может быть только в отдельных нитях, а если опознаёт (например, по типу возврата Task) — то нужно ли было вводить слово async для таких методов?


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

N>Далее, я не вижу, чтобы описывался какой-то интеллект по выходу за пределы этого в случае внешних работ


А он там должен быть?

N>по описанию в MSDN их надо явно переваливать на всякие Task.Run()


Вот уж этого точно не надо, это, по сути, равносильно использованию блокирующего API вместо IOCP.

N>. То есть получается какой-то "домен" асинхронного выполнения, который не имеет чёткого синтаксического отделения от основного. В первую очередь это проблема с библиотеками. Могут ли библиотечные функции блокироваться в таком случае? Если нет, то как им это гарантированно запретить и что делать с такими попытками?


Если честно, не очень понял вопросы.

N>А есть ли средство аккуратной привязки контекста к месту исполнения (такого, как TLS в тредах), или надо тащить все параметры за собой?


Оно к TLS и привязывается, ничего никуда тащить не надо.

N>А как писать своими средствами все эти ReadFileAsync()?


Исходники в большинстве случаев доступны. Там все тривиально. Вот пример:
public Task<byte[]> DownloadDataTaskAsync(Uri address)
{
        // Create the task to be returned
        var tcs = new TaskCompletionSource<byte[]>(address);

        // Setup the callback event handler
        DownloadDataCompletedEventHandler handler = null;
        handler = (sender, e) => HandleCompletion(tcs, e, (args) => args.Result, handler, (webClient, completion) => webClient.DownloadDataCompleted -= completion);
        DownloadDataCompleted += handler;

        // Start the async operation.
        try { DownloadDataAsync(address, tcs); }
        catch
        {
            DownloadDataCompleted -= handler;
            throw;
        }

        // Return the task that represents the async operation
        return tcs.Task;
}

DownloadDataAsync работает через IOCP.

N>Извини, если некоторые вопросы покажутся глупыми, но описание в MSDN написано слишком изиотически, понять по нему детали реализации практически невозможно.


В MSDN много описаний.

N>Почему так сразу и "хуже"?


Ну, чтобы совсем запутать

НС>>Я же тут пытаюсь донести мысль, что то что зеленые потоки могут быть использованы для реализации async/await, не делает async/await зелеными потоками.

N>Ну, кажется, ты споришь с кем-то, кого в этом треде вообще не было.

Кажется.
Re[59]: Зачем Майкрософту рубить сук, на котором он сидит?
От: Ikemefula Беларусь http://blogs.rsdn.org/ikemefula
Дата: 17.05.14 20:54
Оценка: -1
Здравствуйте, Ночной Смотрящий, Вы писали:

I>>Кстати говоря, await можно перепилить на колбеки и всунуть это в таски.


НС>Чего, простите?


Того. Ты в курсе, что await это просто сахар ?

I>> Интересно, как ты в этом случае будешь выкручиваться.


НС>А почему я должен выкручиваться?


Ну это не я асинхронщину реализую через Thread.Sleep()
Re[59]: Зачем Майкрософту рубить сук, на котором он сидит?
От: Ikemefula Беларусь http://blogs.rsdn.org/ikemefula
Дата: 17.05.14 21:10
Оценка: -1
Здравствуйте, Ночной Смотрящий, Вы писали:

НС>Верно. У тебя логическая ошибка — из то что продолжения можно реализовать при помощи кооперативной многозадачности (и даже, для особых извращенцев, наоборот на продолжениях кооперативную многозадачность, запихнув обработку продолжений в цикл, не обязательно цикл выборки сообщений) вовсе не следует что продолжения являются кооперативной многозадачностью.


Ты все еще забыл что речь про асинхронщину, например IOCP. Кооперативная многозадачность именно отсюда и растет. Просто потому, что иначе и способа другого нет — ты явно передаешь управление другому потоку, например шедулеру, или OС и тд и тд.

>Более того, по факту и для CPU bound и для IO bound задач никакой кооперативной многозадачности нет, используются механизмы ОС напрямую с многозадачностью вытесняющей.


Ну и цырк. А сигналы от таймера, пудозреваю, ты собираешься заменить на Thread.Sleep ?

Вобщем, я привел код — покажи где там вытесняющая многозадачность. Ткни пальцем. Начни с Task.Delay

I>>То есть, в кратце, тебе надо пояснить, как будет работать в тасках асинхронный код навроде IOCP или таймера того же.


НС>Не понимаю вопроса. Он будет работать ровно так же, как и без тасков.


Я смотрю ты все время что не понимаешь. Объясняю еще раз — IOCP это асинхронщина в чистом виде. Это, например, может быть вот так
операция 1 зашедулилась
операция 2 зашедулилась
операция 2 выполнилась
операция 1 выполнилась

Вот это IOCP, гарантии тебе никто не даст, что операции сохранят последовательность выполнения.

Тперь фокус, синхронный вариант, см мой JS

write(++read())

Предположим, поток ровно один. write затирает то значение, которое только что прочёл read. Инвариант такой.
Это понятно или надо объяснять ?

операция 1 чтение
операция 1 запись
операция 2 чтение
операция 2 запись

а теперь, внезапно, это становится асинхронным. Какой будет порядок выполнения — а хрен его знает.
может и такой — 1 прочитал, 2 прочитал, 2 записал 1 записал, опаньки, вторая запись затирает совсем не то значение, которое вернул её read
Re[59]: Зачем Майкрософту рубить сук, на котором он сидит?
От: Ikemefula Беларусь http://blogs.rsdn.org/ikemefula
Дата: 17.05.14 21:26
Оценка:
Здравствуйте, Ночной Смотрящий, Вы писали:

I>>покажи, как же таска владеет потоком монопольно.


НС>Я тебе уже наглядно продемонстрировал это. И, на всякий случай, никакого эвентлупа там нет, это консольное приложение.


Ты ничего не показал. У меня код который демонстрирует проблемы. А твой код делает фигню.

НС>Никаких подозрений не появилось?


Нет. Если тебе непонятно, что делает мой код, так и говори.

I>>Неудивительно. await, yield — это прямое указание прерывания текущей задачи


НС>Спасибо, КО. Только при чем тут таски?


Таски умеют работать вот таким вот способом. И этот способ ничем не отличается от того, как работают зеленые потоки.

>Это те самые продолжения, о которых я которое уже письмо твержу. Которые могут быть в том числе и при помощи зеленых потоков реализованы. И которые зелеными потоками не являются, и в современном шарпе реализованы тоже без них.


Не "тоже без них" а умеют ровно всё то же, что и зеленые потоки. Более того, нет способа отличить таски на продолжениях от зеленых потоков.

НС>Что значит "прикрутить к потоку IOCP или таймер" (системный таймер, кстати, тоже через IOCP обычно используют)? И как IOCP в тасках работает в консольном или веб приложении, где никаких эвентлупов нет?


А кто тебе сказал, что там нет эвентлупов ? Ты почти любую функцию дернешь и он сам появится.
Можешь на таймерах проверить. Если он зависнет, значит эвентлупа точно нет Главное с thread.sleep не перепутай
Re[60]: Зачем Майкрософту рубить сук, на котором он сидит?
От: Ночной Смотрящий Россия  
Дата: 17.05.14 22:08
Оценка:
Здравствуйте, Ikemefula, Вы писали:

НС>>Я тебе уже наглядно продемонстрировал это. И, на всякий случай, никакого эвентлупа там нет, это консольное приложение.

I>Ты ничего не показал. У меня код который демонстрирует проблемы. А твой код делает фигню.

А, ну ну. Тогда объясни его вывод с учетом твоей теории про один поток и эвентлуп.

НС>>Никаких подозрений не появилось?

I>Нет.



I> Если тебе непонятно, что делает мой код, так и говори.


Мне неинтересен твой код, потому что код нужен предельно простой.

I>>>Неудивительно. await, yield — это прямое указание прерывания текущей задачи

НС>>Спасибо, КО. Только при чем тут таски?
I>Таски умеют работать вот таким вот способом.

Нет. Это целиком фича компилятора. Таски ничего подобного не делают.

I> И этот способ ничем не отличается от того, как работают зеленые потоки.


Отличается. Зеленые потоки честно сохраняют стек и стейт процессора при переключении, а продолжения преобразуют код в FSM, где код режется на куски. Ничего общего, кроме конечной цели.

I>Не "тоже без них" а умеют ровно всё то же, что и зеленые потоки. Более того, нет способа отличить таски на продолжениях от зеленых потоков.


Есть. Достаточно посмотреть на IL.

НС>>Что значит "прикрутить к потоку IOCP или таймер" (системный таймер, кстати, тоже через IOCP обычно используют)? И как IOCP в тасках работает в консольном или веб приложении, где никаких эвентлупов нет?

I>А кто тебе сказал, что там нет эвентлупов ?

Я тебе сказал.

I> Ты почти любую функцию дернешь и он сам появится.


Продемонстрируй на том примере, что я привел.

I>Можешь на таймерах проверить. Если он зависнет, значит эвентлупа точно нет


Ты про который таймер? Который винформсный что ли?
Re[65]: Зачем Майкрософту рубить сук, на которых он сидит?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 18.05.14 06:09
Оценка:
Здравствуйте, Ночной Смотрящий, Вы писали:

N>>"С точки зрения тасков" это вообще долгая задача.

НС>Шо, с любым значением параметра?

В общем-то да, а что удивляет? Любая дополнительная задержка — помеха этому механизму. Даже вызов типа sleep(0), который в куче мест значит yield(), откладывает завершение задачи и освобождение исполняющей нити.
На практике, конечно, можно полагаться на кратковременность некоторых задержек типа чтения из файла на локальном диске, но всё равно можно помнить, что это хак. Особенно если такой пул один на весь процесс.

N>>>> Это чистое IO, и эффект от него такой же, как если бы задача сделала блокирующий read().

НС>>>Ну так не надо делать блокирующий read.
N>>О! Что и требовалось показать.
НС>Зачем тебе потребовалось эту очевидность показать?

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

В начале этой субветки меня заинтересовала фраза Ikemefula@ "Таски и есть легкие потоки". Её можно было понять разными вариантами. Например, очевидная экономия на создании и удалении нитей (которые обычно сильно дороже взятия готовых из пула). Работой поверх волокон (fibers) или местной реализации зелёных нитей. Явной подпоркой от компилятора (как уже убедились в основном по твоим рассказам, это случай не TPL, а async/await). Выбор между этими вариантами интересен с точки зрения "посмотреть, как же они делают", но для начала надо было продраться через перекосы стилей описания.
Сейчас я вижу, что скорее всего он ошибся ("таски" работают поверх обычных нитей, блокирующие вызовы не отлавливаются, поддержка компилятора не у них, а у async/await). Я правильно понял картину?
Если да, то сейчас самое интересное мне это можно ли оба механизма (что TPL, что async/await) регулировать: какая группа нитей их исполняет; какое управление пулом, особенно в маргинальных случаях типа "все нити заняты, а надо ещё что-то делать"; возможны ли клинчи (deadlocks) механизма управления; как это сочетается с сериализацией доступа к данным; и ещё много подобных вопросов.

НС>Честные ветки никуда не делись. А зеленые на Thread.Sleep прореагируют точно так же, как и TPL.


То есть зелёные у вас не умеют ловить блокирующие вызовы, понятно. А о какой именно их реализации ты говоришь в данном случае? Fibers?

НС>Еще раз, по буквам, уж не знаю как еще объяснить — таски вообще никак не связаны с кооперативной многозадачностью, абсолютно. Они обеспечивают эффективную реализацию fine grained параллелизма с удобным API, и все. А все игры с await — совсем другая механика.


Вот опять ты понимаешь термин "кооперативная многозадачность" в крайне узком и странном смысле. Я не вижу причин так сужать в принципе; для меня кооперативная многозадачность это когда нужно явно писать код в стиле "я хочу сейчас отдать управление". Это же соответствует википедии: "Тип многозадачности, при котором следующая задача выполняется только после того, как текущая задача явно объявит себя готовой отдать процессорное время другим задачам" и многим другим источникам. По этому определению async/await вообще не механизм кооперативной многозадачности: никто не обязывает его делать через неё; никто не обязывает при await отдавать управление (а может, результат уже готов? тогда его можно сразу и забрать). А вот то, что в пределах "тасков" рекомендуется не делать никаких блокирующих действий, а выносить их отдельно и/или пользоваться всеми этими APM и EAP — делает их ближе к тому, что под кооперативной многозадачностью понимает большинство.

N>>Это, действительно, слишком грубое объяснение. Потому что не покрывает даже вход в асинхронный контекст — вызов async функции из синхронного кода.

НС>А почему оно должно его покрывать? Это личное дело контекста.

Я про то, что покрывает объяснение, а не выполнение

НС> И контекст, в том числе, может вообще ничего не делать.


Ну что-то он таки делает. Как мы убедились по соседнему
Автор: Ночной Смотрящий
Дата: 17.05.14
твоему сообщению, оба async метода стартовали в отдельных тредах. Это значит (вполне логично), что вызывающий не-async метод не подпадает под описанный тобой подход "разрезать на части, пригодные для включения в FSM управления".

N>> Если компилятор не опознаёт, что здесь идёт вызов именно async функции, то вся эта кусочно-гнездовая развесистость может быть только в отдельных нитях, а если опознаёт (например, по типу возврата Task) — то нужно ли было вводить слово async для таких методов?

НС>Во-первых компилятор ничего не опознает, он делит код строго по await и никак иначе.

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

НС> Во-вторых async у методов действительно не обязателен, это уже вопросы контроля и читаемости, у Липперта в блоге есть на эту тему пост.


Ссылку, plz.

N>>Далее, я не вижу, чтобы описывался какой-то интеллект по выходу за пределы этого в случае внешних работ

НС>А он там должен быть?

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

N>>. То есть получается какой-то "домен" асинхронного выполнения, который не имеет чёткого синтаксического отделения от основного. В первую очередь это проблема с библиотеками. Могут ли библиотечные функции блокироваться в таком случае? Если нет, то как им это гарантированно запретить и что делать с такими попытками?

НС>Если честно, не очень понял вопросы.

Ну ответь по поводу типовых проблем абзацем выше, тогда вернёмся.

N>>А есть ли средство аккуратной привязки контекста к месту исполнения (такого, как TLS в тредах), или надо тащить все параметры за собой?

НС>Оно к TLS и привязывается, ничего никуда тащить не надо.

Как привязывается? Async метод наследует TLS вызывающего?

N>>А как писать своими средствами все эти ReadFileAsync()?

НС>Исходники в большинстве случаев доступны. Там все тривиально. Вот пример:
НС>public Task<byte[]> DownloadDataTaskAsync(Uri address)
[...]
НС> try { DownloadDataAsync(address, tcs); }
Сепульки — это для сепуления.
НС>DownloadDataAsync работает через IOCP.

Вот именно как он "работает через IOCP" и было интересно. Есть примеры реализации?
The God is real, unless declared integer.
Re[61]: Зачем Майкрософту рубить сук, на котором он сидит?
От: Ikemefula Беларусь http://blogs.rsdn.org/ikemefula
Дата: 18.05.14 06:14
Оценка: -1
Здравствуйте, Ночной Смотрящий, Вы писали:

I>>Ты ничего не показал. У меня код который демонстрирует проблемы. А твой код делает фигню.


НС>А, ну ну. Тогда объясни его вывод с учетом твоей теории про один поток и эвентлуп.


Сначала ты объясни что в моем коде, я уже жду этого два дня, а потом я посмотрю твой код.

I>> Если тебе непонятно, что делает мой код, так и говори.


I>>Таски умеют работать вот таким вот способом.


НС>Нет. Это целиком фича компилятора. Таски ничего подобного не делают.


await это просто сахар, подумай головой. До введения await можно было написать ровно то же.

НС>Отличается. Зеленые потоки честно сохраняют стек и стейт процессора при переключении, а продолжения преобразуют код в FSM, где код режется на куски. Ничего общего, кроме конечной цели.


Ага, зеленые потоки это какая то магия

I>>Не "тоже без них" а умеют ровно всё то же, что и зеленые потоки. Более того, нет способа отличить таски на продолжениях от зеленых потоков.


НС>Есть. Достаточно посмотреть на IL.


Нужны качественные отличия, а не другой IL

I>>А кто тебе сказал, что там нет эвентлупов ?


НС>Я тебе сказал.


Тест на таймере говорит что ты приврал малёк.

I>> Ты почти любую функцию дернешь и он сам появится.

НС>Продемонстрируй на том примере, что я привел.

I>>Можешь на таймерах проверить. Если он зависнет, значит эвентлупа точно нет


НС>Ты про который таймер? Который винформсный что ли?


Раскрой, наконец, глаза.
Re[61]: Зачем Майкрософту рубить сук, на котором он сидит?
От: Ikemefula Беларусь http://blogs.rsdn.org/ikemefula
Дата: 18.05.14 07:08
Оценка:
Здравствуйте, Ночной Смотрящий, Вы писали:

I>> Если тебе непонятно, что делает мой код, так и говори.


НС>Мне неинтересен твой код, потому что код нужен предельно простой.


Там и есть предельно простой код — запускается несколько асинхронных операций одновременно.
Что может быть проще — не ясно

В кратце — мой пример требует примерно вот такое решение

lock(sync)
{
  var x = await ReadAsync();
  await Write(++x);
}


Но, как ты в курсе, lock нельзя использовать в этом контексте, а в TPL нет примитивов синхронизации для асинхронного кода

Парадокс — асинхронный код писать можно, а асинхронных примитивов синхронизации нет

Теперь про IOCP — все проблемы один к одному распространяются на асинхронщину через IOCP.

Пока одна операция в шедулере, паралельная может даже успеть отработать. Что будет с разделяемым ресурсом — не ясно.
Re[49]: Зачем Майкрософту рубить сук, на котором он сидит?
От: Ikemefula Беларусь http://blogs.rsdn.org/ikemefula
Дата: 18.05.14 07:34
Оценка:
Здравствуйте, netch80, Вы писали:

I>>Внезапно, даже в однопоточной модели возникают гонки в силу кооперативной многозадачности. В зеленых потоках эту проблему проще контролировать.


N>Почему проще?


Проще в сравнении с олдскульным APM и всяким колбеками, точнее способом их реализации в том же дотнете например.

Вот например есть такая хрень
// чтение-запись с разделяемым ресурсом
lock(sync) {
  var x = await ReadAsync();
  await WriteAsync(Modify(x));
}


Сейчас нет возможности вставить lock. Отсюда получается фокус — разделяемый ресурс есть, а как с ним внятно работать, надо еще подумать.

В зеленом потоке нет никакой проблемы реализовать этот lock, без разницы, что унутре зеленого потока — continuation или coroutine.

Собственно, в тасках это тоже реализуемо, http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx

Все это, естественно, для асинхронного кода. Для Thread.Sleep это не нужно

Собственно меня сильно смущает, что в тасках реализовали всё подряд, а вот простую асинхронную модель как то забыли поддержать.
Re[66]: Зачем Майкрософту рубить сук, на которых он сидит?
От: Ночной Смотрящий Россия  
Дата: 18.05.14 09:32
Оценка:
Здравствуйте, netch80, Вы писали:

N>В условиях, когда один из собеседников (ты) понимает и применяет термины


Традиционно ты скатился на спор о терминах. Дальше без меня.

N>Сейчас я вижу, что скорее всего он ошибся ("таски" работают поверх обычных нитей, блокирующие вызовы не отлавливаются, поддержка компилятора не у них, а у async/await). Я правильно понял картину?


Да.

N>Если да, то сейчас самое интересное мне это можно ли оба механизма (что TPL, что async/await) регулировать: какая группа нитей их исполняет; какое управление пулом, особенно в маргинальных случаях типа "все нити заняты, а надо ещё что-то делать";


Да.

N> возможны ли клинчи (deadlocks) механизма управления;


Да.

N> как это сочетается с сериализацией доступа к данным;


Перпендикулярно.

N> и ещё много подобных вопросов.


Задавай.

N>Я про то, что покрывает объяснение, а не выполнение


Зачем в объяснении общих механизмов включать переменную специфику?

N>Ну что-то он таки делает.


В консольных приложениях — ничего.

N>Это значит (вполне логично), что вызывающий не-async метод не подпадает под описанный тобой подход "разрезать на части, пригодные для включения в FSM управления".


Очевидно, раз там await отсутствует. При чем тут контекст?

N>А что в таком случае происходит при вызове методов типа XxxAsync()?


Что угодно. В случае IOCP просто создается операция и возвращается TaskCompletionSource, завязанная на колбек.

N>А вместо этого мы видим категорическое использование отдельной нити. Как-то странно получается.


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

N>Ссылку, plz.


http://blogs.msdn.com/b/visualstudio/archive/2011/02/07/8-part-async-series-by-eric-lippert.aspx

В какой конкретно части речь про модификатор async я не помню.

N>По-нормальному они должны быть все адресованы, проговорены, продуманы и описаны, включая типовые решения.


Многое из этого там присутствует, в управляющих механизмах TPL много всяких интересных алгоритмов. Но это уже детали, которые в контексте данного разговора нет смысла обсуждать. Интересно — можешь сам покопаться, информации в инете полно.

N>>>А есть ли средство аккуратной привязки контекста к месту исполнения (такого, как TLS в тредах), или надо тащить все параметры за собой?

НС>>Оно к TLS и привязывается, ничего никуда тащить не надо.
N>Как привязывается? Async метод наследует TLS вызывающего?

Да. Либо можно явно привязать/отвязать.

N>Сепульки — это для сепуления.


Что тебе непонятно? Код тривиальный.

НС>>DownloadDataAsync работает через IOCP.

N>Вот именно как он "работает через IOCP" и было интересно.

А это уже за пределами TPL, это старый древний код еще из первой версии фреймворка.

N> Есть примеры реализации?


public void DownloadDataAsync(Uri address, object userToken)
{
        if(Logging.On)Logging.Enter(Logging.Web, this, "DownloadDataAsync", address);
        if (address == null) 
                throw new ArgumentNullException("address");
        InitWebClientAsync();
        ClearWebClientState();
        AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(userToken);
        m_AsyncOp = asyncOp;
        try {
                WebRequest request = m_WebRequest = GetWebRequest(GetUri(address));
                DownloadBits(request, null, new CompletionDelegate(DownloadDataAsyncCallback), asyncOp);
        } catch (Exception e) {
                if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) {
                        throw;
                }
                if (!(e is WebException || e is SecurityException)) {
                        e = new WebException(SR.GetString(SR.net_webclient), e);
                }
                DownloadDataAsyncCallback(null, e, asyncOp);
        }
        if(Logging.On)Logging.Exit(Logging.Web, this, "DownloadDataAsync", null);
}

...

private byte[] DownloadBits(WebRequest request, Stream writeStream, CompletionDelegate completionDelegate, AsyncOperation asyncOp) {
        WebResponse response = null;
        DownloadBitsState state = new DownloadBitsState(request, writeStream, completionDelegate, asyncOp, m_Progress, this);

        if (state.Async) {
                request.BeginGetResponse(new AsyncCallback(DownloadBitsResponseCallback), state);
                return null;
        } else {
                response = m_WebResponse = GetWebResponse(request);
        }

        bool completed;
        int bytesRead = state.SetResponse(response);
        do {
                completed = state.RetrieveBytes(ref bytesRead);
        } while (!completed);
        state.Close();
        return state.InnerBuffer;
}


BeginGetResponse — unmanaged метод, создает IOCP порт на сокет и вешает на него колбек из AsyncCallback. Все тривиально, что ты пытаешься найти мне до сих пор непонятно.
Ну и код захвата контекста, раз уж он тебя интересует:
AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(userToken);

Если контекст не позволяет многопоточного исполнения, то колбек отмаршалится тем или иным способом в основной поток.
Re[67]: Зачем Майкрософту рубить сук, на которых он сидит?
От: Ikemefula Беларусь http://blogs.rsdn.org/ikemefula
Дата: 18.05.14 10:56
Оценка:
Здравствуйте, Ночной Смотрящий, Вы писали:

N>>В условиях, когда один из собеседников (ты) понимает и применяет термины


НС>Традиционно ты скатился на спор о терминах. Дальше без меня.


Разница в понимании того, как работает дотнет. Поэтому разрешение вопроса в принципе невозможно без уточнения терминов.


НС>
НС>AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(userToken);
НС>

НС>Если контекст не позволяет многопоточного исполнения, то колбек отмаршалится тем или иным способом в основной поток.

Вот здесь и начаинается самое интересное — в силу этой асинхронщины две операции могут поменяться местами и это даст обычные гонки.

То есть, в кратце, таски обязаны поддерживать кооперативную многозадачность. Как именно это делают это в другом сообщении.
Re[68]: Зачем Майкрософту рубить сук, на которых он сидит?
От: Ночной Смотрящий Россия  
Дата: 18.05.14 13:01
Оценка:
Здравствуйте, Ikemefula, Вы писали:

I>Вот здесь и начаинается самое интересное


Оно может и начинается, только это старый добрый дотнет 2.0, а никак не таски.

I> — в силу этой асинхронщины две операции могут поменяться местами и это даст обычные гонки.


Могут.

I>То есть, в кратце, таски обязаны поддерживать кооперативную многозадачность.


Нет.
Re[69]: Зачем Майкрософту рубить сук, на которых он сидит?
От: Ikemefula Беларусь http://blogs.rsdn.org/ikemefula
Дата: 18.05.14 14:24
Оценка: -1
Здравствуйте, Ночной Смотрящий, Вы писали:

I>>Вот здесь и начаинается самое интересное


НС>Оно может и начинается, только это старый добрый дотнет 2.0, а никак не таски.


Правильно, о том и речь. Старый дотнет не знает про таски, а потому строит вызовы на колбеках -> кооперативная многозадачность. Вот отсюда она и растет.

I>> — в силу этой асинхронщины две операции могут поменяться местами и это даст обычные гонки.


НС>Могут.


Итого:

Ты получил ответ на свой вопрос

НС>А что там такого извратного?

В кратце — асинхронная цепочка в таком вот стиле это АДЪ.



I>>То есть, в кратце, таски обязаны поддерживать кооперативную многозадачность.


НС>Нет.


См. свой ответ выше "это старый добрый дотнет 2.0"

Если таски не поддерживают кооперативную многозадачность, значит они не могут работать с функциями навроде той, что ты показал. Потому как именно она навязывает эту самую кооперативную многозадачность.
Re[52]: Зачем Майкрософту рубить сук, на котором он сидит?
От: Sinix  
Дата: 19.05.14 06:02
Оценка:
Здравствуйте, netch80, Вы писали:

N>См. мой предыдущий ответ. С тасками названного тобой образца заполнение всех нитей ожиданием I/O заблокирует пул в целом.

Неа, 1 поток связан с кучей IOCP, по 1 потоку на ядро процессора, по умолчанию продолжение выполняется в отдельном потоке пула (но если хочется извратиться — никто не мешает).

Чему там блокироваться? И при чём тут кооперативная многозадачность? Напомню, ув Ikemefula вторую страницу рассказывает, как по его мнению устроены таски, исходя из убеждения, что таски === зелёные потоки, только плохо написанные.

N>Более того, ожидание чего-то через Task.Wait() может дать такой же результат, если нет свободных нитей, и поставленная в очередь задача не может из-за этого выполниться. Если егойный task manager решает это временным порождением новых нитей, то это грязный хак, не отменяющий порочность механизма (и ещё неизвестно, сколько ему надо, чтобы понять ситуацию). Если же он в состоянии при любом переходе исполняющей нити в ожидание переключать эту нить на другую задачу — то фактически мы имеем подложкой под task manager — не OS threads, а green threads.


Тут путаница Task.Wait() — это не ожидание, это блокировка текущего потока до завершения задачи. Чтобы продолжить выполнение асинхронно, по завершению задачи используется Task.ContinueWith() или await. В этом случае никаких блокировок не будет.

Если речь про ожидание завершения внутри самой задачи, то как правило оно перекидывается на само API фреймфорка (которое в итоге работает через всё те же IOCP). Другими словами: если у класса есть async-API, то он не должен его реализовывать через блокирующие операции. Именно по той причине, что ты написал выше.


N>(BTW, что такое "вижла"? Visual Studio или что-то другое? я вашего жаргона не знаю)

Это лучше у Ikemefula спросить, из контекста — да, Visual Studio.

S>>В тасках ровно одна модель. За неё прячется практически что угодно — от обычных потоков до акторов и динамического раскидывания вызовов по кластеру. Только не надо начинать за "это всё не нужно"

N>Мнэээ... там таки есть гарантия, что спящая в ожидании задача освободит тред, или нет?
Угу. Если конечно автор кода не расставил блокировок по вкусу.

I>>>Таски нельзя отладить — речь была про APM-модель. Будет гаранировано АДЪ. Собственно, с тасками у микрософта пока что не сильно лучше.

S>>Ты 100% не использовал Parallel Stacks Window. Т.е. с тасками по сути не работал, о чём и было сказано выше. Про отладку в VS 1013 с поддержкой awiat и прочих плюшек даже начинать не буду.
N>Простите, а с каких это пор неиспользование возможности _отладчика_ для "тасков" означает неработу с ними "по сути"?

Task list + Stacks Window — первое, что упоминается в документации по отладке для тасков. Ну и в студии в режиме отладки Task list — на самом видном месте, не заметить сложно.

Проблема в том, что у Ikemefula по ветке полно очень странных утверждений: с одной стороны уверенность заоблачная и все возражения тупо отметаются, с другой — что ни фраза, то пальцем в небо.
Согласись, глупо слушать про "таски нельзя отладить" от человека, который с ними работать и не пытался
Re[67]: Зачем Майкрософту рубить сук, на которых он сидит?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 19.05.14 06:10
Оценка:
Здравствуйте, Ночной Смотрящий, Вы писали:

N>>В условиях, когда один из собеседников (ты) понимает и применяет термины

НС>Традиционно ты скатился на спор о терминах. Дальше без меня.

Это закономерно в подобных ситуациях, потому что у MS является традицией использовать термины в понимании "чуть не так", чем по остальным участникам отрасли. После этого любой спор о потрохах приводит к тому, что приходится выяснять, что же имелось в виду. И тут оказывается, что "таски" это не задачи, а конкретная TPL; "асинхронное" это не асинхронное вообще, а конкретно async+await; и тэ дэ и тэ пэ. На то, чтобы вернуть всё на место на свои ноги, требуются время и усилия.

В любом случае я буду формулировать вопросы по мере поступления времени и настроения, а вот будешь ли ты на них отвечать и как именно отвечать — посмотрим

N>>Если да, то сейчас самое интересное мне это можно ли оба механизма (что TPL, что async/await) регулировать: какая группа нитей их исполняет; какое управление пулом, особенно в маргинальных случаях типа "все нити заняты, а надо ещё что-то делать";

НС>Да.

Что именно "да"? Я тут задал минимум два вопроса.

N>>Я про то, что покрывает объяснение, а не выполнение

НС>Зачем в объяснении общих механизмов включать переменную специфику?

Так ты ж уже начал её рассказывать, при том, что у тебя не было сказано, что это "переменная специфика". И тут же в твоём примере оказалось, что оно работает заметно иначе, чем можно было бы предположить из прямого понимания твоего описания. Ты описывал про "разрезание" кода на части, переключающиеся как FSM под крышей общего шедулера. И тут оказывается, что в простейшем примере оба(!) два запущенных async-метода выполняются в отдельных нитях, не совпадающих с главной нитью. Если бы ты сказал "компилятор имеет право разрезать, а имеет — просто вынести в отдельные нити, в пул, etc. и ждать результата через стандартную синхронизацию; считайте, что он умеет выбирать оптимальный метод" — было бы честнее и проще. Но ты этого не говоришь.

N>>Ну что-то он таки делает.

НС>В консольных приложениях — ничего.

В чём тут принципиальность отличия консольных приложений от других?
В консольных не может быть асинхронных событий? А почему собственно?
С другой стороны, чем не-консольные приложения (а какие? flat GUI? WPF? что-то иное?) отличаются? Там можно реагировать через K секунд на нажатие кнопки?

N>>Это значит (вполне логично), что вызывающий не-async метод не подпадает под описанный тобой подход "разрезать на части, пригодные для включения в FSM управления".

НС>Очевидно, раз там await отсутствует. При чем тут контекст?

Теперь тебе это "очевидно". Хотя объяснял ты совершенно иначе.

N>>Ссылку, plz.

НС>http://blogs.msdn.com/b/visualstudio/archive/2011/02/07/8-part-async-series-by-eric-lippert.aspx

Вода мутная, одна штука. Очень мало внятных подробностей. С другой стороны, есть явно противоречащие вещи: "The whole point of async methods it that you stay on the current thread as much as possible." из "Whence await?" не согласуется с запуском первого async метода в отдельной нити.

N>>По-нормальному они должны быть все адресованы, проговорены, продуманы и описаны, включая типовые решения.

НС>Многое из этого там присутствует, в управляющих механизмах TPL много всяких интересных алгоритмов. Но это уже детали, которые в контексте данного разговора нет смысла обсуждать. Интересно — можешь сам покопаться, информации в инете полно.

Это ты про TPL. А про async/await? Ничто же формально не мешает запустить через async миллион работ типа "проверить живость вот такого URL".

N>>>>А есть ли средство аккуратной привязки контекста к месту исполнения (такого, как TLS в тредах), или надо тащить все параметры за собой?

НС>>>Оно к TLS и привязывается, ничего никуда тащить не надо.
N>>Как привязывается? Async метод наследует TLS вызывающего?
НС>Да. Либо можно явно привязать/отвязать.

Явно — это неинтересно, а вот наследование TLS при возможности реального исполнения в другой нити... что-то в этом есть парадоксальное.

НС>public void DownloadDataAsync(Uri address, object userToken)

[...]
НС> if (state.Async) {
НС> request.BeginGetResponse(new AsyncCallback(DownloadBitsResponseCallback), state);

Ага, вот и оно. То есть старые методы, которые вроде бы deprecated, никуда не делись и их просто завернули в обёртки. Спасибо.
Тогда ещё один вопрос. Зачем ко всяким BeginRead, BeginWrite обязательно парные EndRead, EndWrite и так далее, а не просто EndIO?

НС>BeginGetResponse — unmanaged метод, создает IOCP порт на сокет

~~~~~~~~~
О'кей, тут механика понятна.

НС> Все тривиально, что ты пытаешься найти мне до сих пор непонятно.


Ну за неимением опыта с данной платформой я не знаю, как формулировать поисковые запросы. Но на сейчас результат понятен.
Жаль, что работа с IOCP сделана unmanaged. Подозреваю, что её честный вынос на managed уровень дал бы возможность делать не менее удобные штуки своими силами, без маскирующего и местами запутывающего слоя.
The God is real, unless declared integer.
Re[54]: Зачем Майкрософту рубить сук, на котором он сидит?
От: Sinix  
Дата: 19.05.14 06:19
Оценка:
Здравствуйте, netch80, Вы писали:

S>> Запрос инициируется с рабочего потока, запихивается в очередь iocp, по завершению — продолжает выполнение в потоке из IOCP-пула. Кооперативной многозадачности тут нет: обработчики iocp будут выполняться параллельно вплоть до исчерпания пула потоков.


N>Не-а. Всё описанное тобой получается только в случае если ты сам, явно, определишь какой-то "пул" ниток, которые не будут делать ничего кроме как дёргать GetQueuedCompletionStatus(); но это будет _твоё_, как разработчика, решение. В то же время, если ты сделаешь в одной нити

N>* создание IOCP
N>* создание и связь с этим IOCP некоторых начальных генераторов событий, типа слушающего сокета
N>* цикл типа "пока не приказали посинеть" из ожидания события и его обработки, включая помещение новых асинхронных операций с оповещением через тот же IOCP объект

Ну, т.е. разработаю свой пул для тасков с 0, причём пул, заточенный под обработку кучи очень коротких (или редких) сообщений (иначе забьётся нафиг) Мы там выше говорим за стандартную реализацию в .NET, не про сфероконей.

Кстати, я там слишком сильно упростил — по умолчанию потоки для IOCP запускают продолжение завершившейся задачи в потоке из "нормального" пула. Именно чтобы не засорять iocp

N>у тебя получится ровно то, что говорит Ikemefula: "асинхронщина" через кооперативную многозадачность собственного исполнения. При этом ничто не мешает сделать несколько таких нитей и распределить работу между ними (например, раскидывая входящие соединения), каждую привязывать к своему IOCP.

Угу, нечто типа эвентлупа com/winforms. В принципе, можно, но в общем случае работать будет хуже, чем готовые таски.

N>А если не ограничиваться одной нитью, то всё равно получается "асинхронщина", но уже с усложнением (синхронизация доступа к общим ресурсам и т.п.)

Угу. Поэтому в tpl обычно не используют обшие изменяемые ресурсы, или запускают задачи, работающие с этим ресурсом через отдельный шедулер. Который и рулит блокировками или тупо запускает все задачи в одном потоке (тут в теории всплывают грабли с реэнтерабельностью, на практике они сами собой разруливается через всё тот же .ContinueWith()).
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.