Данный код виснет, т.к. task.Wait() tcs.SetResult() оказываются на одном стеке (соотв. в одном потоке).
Более того, никакие опции типа TaskCreationOptions.LongRunning | TaskCreationOptions.RunContinuationsAsynchronously не помогают.
Почему так? Т.е. почему все оказалось за инлайнено? Если закомментировать //1 и раскомментировать //2 то все ожидаемо работает...
Есть какая-то спецификация на это поведение, как глядя на данный код можно понять, что здесь deadlock?
Проблема в том, что продолжение tcs.SetResult(10) будет в том же потоке, что и task.
Можно продолжить в отдельном таске и на всякий случай await tcs.Task.ConfigureAwait(false).
S> Проблема в том, что продолжение tcs.SetResult(10) будет в том же потоке, что и task. S>Можно продолжить в отдельном таске и на всякий случай await tcs.Task.ConfigureAwait(false).
Здравствуйте, alexander_r, Вы писали:
_>просто await task; _>ну или .ContinueWith(t => task.Wait())
Это понятно, но почему в исходном случае deadlock? Почему все на одном стеке? Как это можно понять не запуская код, т.к. данная ситуация стабильно вопроизводится, следовательно есть
какая-то неявная особенность.
Здравствуйте, Sharov, Вы писали:
S>Здравствуйте, alexander_r, Вы писали:
_>>просто await task; _>>ну или .ContinueWith(t => task.Wait())
S>Это понятно, но почему в исходном случае deadlock? Почему все на одном стеке? Как это можно понять не запуская код, т.к. данная ситуация стабильно вопроизводится, следовательно есть S>какая-то неявная особенность.
так вы же вызываете await tcs.Task; он и переключает все в один поток
т.е или tcs.Task.Wait(), task.Wait() или await tcs.Task, await task
S>Это понятно, но почему в исходном случае deadlock? Почему все на одном стеке? Как это можно понять не запуская код, т.к. данная ситуация стабильно вопроизводится, следовательно есть S>какая-то неявная особенность.
async void Test()
{
var tcs = new TaskCompletionSource<int>();
var task = Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
tcs.SetResult(10); // Таска (из task completion source) завершилась и синхронно запустился сontinuation (1)
// task ждет завершения continuation (1) (deadlock)
Console.WriteLine("here3");
});
// await tcs.Task раскроется во что-то типа
tcs.Task.GetAwaiter().OnCompleted(() => /* (1) continuation */
{
// continuation ждет завершения task (deadlock)
task.Wait();
});
}
// Если написатьvar tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
// То continuation будет запускаться на другом потоке и дедлока не будет
Здравствуйте, alexander_r, Вы писали:
S>>Это понятно, но почему в исходном случае deadlock? Почему все на одном стеке? Как это можно понять не запуская код, т.к. данная ситуация стабильно вопроизводится, следовательно есть S>>какая-то неявная особенность. _>так вы же вызываете await tcs.Task; он и переключает все в один поток _>т.е или tcs.Task.Wait(), task.Wait() или await tcs.Task, await task
Я ожидаю на tcs.Task, которой должно проставиться значение 10 и task завершился, после чего за await отработает task.Wait().
Оказывается, что tcs.SetResult(10) ждет task.Wait() --
Здравствуйте, RushDevion, Вы писали:
S>>Это понятно, но почему в исходном случае deadlock? Почему все на одном стеке? Как это можно понять не запуская код, т.к. данная ситуация стабильно вопроизводится, следовательно есть S>>какая-то неявная особенность.
RD>
RD>async void Test()
RD>{
RD> var tcs = new TaskCompletionSource<int>();
RD> var task = Task.Factory.StartNew(() =>
RD> {
RD> Thread.Sleep(1000);
RD> tcs.SetResult(10); // Таска (из task completion source) завершилась и синхронно запустился сontinuation (1)
RD> // task ждет завершения continuation (1) (deadlock)
RD> Console.WriteLine("here3");
RD> });
RD> // await tcs.Task раскроется во что-то типа
RD> tcs.Task.GetAwaiter().OnCompleted(() => /* (1) continuation */
RD> {
RD> // continuation ждет завершения task (deadlock)
RD> task.Wait();
RD> });
RD>}
RD>// Если написать
RD>var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
RD>// То continuation будет запускаться на другом потоке и дедлока не будет
RD>
Круто -- . Но вот как это все можно понять просто глядя на код? Да, если иметь подобный опыт выстрелов себе в ногу, то возможно такое поймать. А если где-то написал такое, выглядит более-менее нормально,
но это какой-то сильно нечасто вызываемый фрагмент кода, тестов особых нет и т.д. Никаких предупреждений от компилятора, R#, ничего. А меж тем куча неявных вещей проиходит, куча...
В целом, я не заметил\не знал\забыл, что после SetResult() будет еще вызов продолжения, которое меня и подвело...
Здравствуйте, Sharov, Вы писали:
S>Круто -- . Но вот как это все можно понять просто глядя на код? Да, если иметь подобный опыт выстрелов себе в ногу, то возможно такое поймать. А если где-то написал такое, выглядит более-менее нормально, S>но это какой-то сильно нечасто вызываемый фрагмент кода, тестов особых нет и т.д. Никаких предупреждений от компилятора, R#, ничего. А меж тем куча неявных вещей проиходит, куча... S>В целом, я не заметил\не знал\забыл, что после SetResult() будет еще вызов продолжения, которое меня и подвело...
Мне кажется, что TPL разработана под какие-то более простые сценарии работы.
Т.е. если хочется прямо вручную контролировать, что в каком потоке — то проще и надёжнее по-старинке, с запуском тредов.
А все эти await и async рассчитаны на то, что ты не будешь вручную делать штуки типа SetResult и внутри таски обращаться к другим таскам. Потому что очень быстро можно потеряться, кто от кого зависит, и нет ли скрытых ожиданий на самих себе.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, Sharov, Вы писали:
S>А все эти await и async рассчитаны на то, что ты не будешь вручную делать штуки типа SetResult и внутри таски обращаться к другим таскам. Потому что очень быстро можно потеряться, кто от кого зависит, и нет ли скрытых ожиданий на самих себе.
Дело не SetResult, а конкретно в Task.Wait()/Task.GetResult(). Хотите, чтоб не было дедлоков — забудьте про эти методы
S>Мне кажется, что TPL разработана под какие-то более простые сценарии работы. S>Т.е. если хочется прямо вручную контролировать, что в каком потоке — то проще и надёжнее по-старинке, с запуском тредов. S>А все эти await и async рассчитаны на то, что ты не будешь вручную делать штуки типа SetResult и внутри таски обращаться к другим таскам. Потому что очень быстро можно потеряться, кто от кого зависит, и нет ли скрытых ожиданий на самих себе.
Удивляет почему нету никаких подсказок от студии, мол вот это таска, а это ее продолжение. И как бы они чем-то одинаковым были выделены.
Здравствуйте, RushDevion, Вы писали:
RD>// Если написать RD>var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously); RD>// То continuation будет запускаться на другом потоке и дедлока не будет
Точно? По логике должно было быть так, но я пробовал по данному примеру так делать — и оно все равно запускалось в том же потоке. Что меня весьма удивило.
F>Точно? По логике должно было быть так, но я пробовал по данному примеру так делать — и оно все равно запускалось в том же потоке. Что меня весьма удивило.
Пожалуй, не совсем корректно выразился.
Суть в том, что continuation именно асинхронно будет выполняться (а не сразу же, как parent-таска завершится), а зашедулится оно может и на том же потоке. Вот рекомендую почитать, если есть желание более детально разобраться.
Здравствуйте, fmiracle, Вы писали:
F>Здравствуйте, RushDevion, Вы писали:
RD>>// Если написать RD>>var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously); RD>>// То continuation будет запускаться на другом потоке и дедлока не будет
F>Точно? По логике должно было быть так, но я пробовал по данному примеру так делать — и оно все равно запускалось в том же потоке. Что меня весьма удивило.
Здравствуйте, Sharov, Вы писали:
RD>>>// Если написать RD>>>var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously); RD>>>// То continuation будет запускаться на другом потоке и дедлока не будет F>>Точно? По логике должно было быть так, но я пробовал по данному примеру так делать — и оно все равно запускалось в том же потоке. Что меня весьма удивило. S>Т.е. опять же был deadlock?
Здравствуйте, RushDevion, Вы писали:
F>>Точно? По логике должно было быть так, но я пробовал по данному примеру так делать — и оно все равно запускалось в том же потоке. Что меня весьма удивило. RD>Пожалуй, не совсем корректно выразился. RD>Суть в том, что continuation именно асинхронно будет выполняться (а не сразу же, как parent-таска завершится), а зашедулится оно может и на том же потоке. RD>Вот рекомендую почитать, если есть желание более детально разобраться.
Я неправильно сказал видимо. Проблема была в том, что независимо от того ставилось RunContinuationsAsynchronously или нет, после SetResult всегда вызывалось продолжение (1) (которое ставит task.Wait) в том же потоке — и только после него продолжалось выполнение кода дальше после SetResult и тоже в том же потоке — итого дедлок.
Хотя по логике получалось что должен был завершиться код который после SetResult и отдельно запланироваться запуск продолжения.
вывод:
T1 run test
T1 task started, awaiting
T3 processing task..
T3 before SetResult..
T3 cont, before Wait
часть данных не выведена из-за дедлока.
При замене task.Wait() на await task — все корректно проходит, но все равно синхронно:
T1 run test
T1 task started, awaiting
T3 processing task..
T3 before SetResult..
T3 cont, before Wait
T3 task done
T3 cont, after Wait
Как-то это все же странно я бы ожидал что при TaskContinuationOptions.RunContinuationsAsynchronously должно быть без дедлока и вывод типа:
TX before SetResult..
TX task done
TY cont, before Wait
TY cont, after Wait
где X может быть равен Y а может и нет. Но главное что если поток один, то последовательность операций такая.
А оно не так.
Где я неправ?