Почему будет deadlock?
От: Sharov Россия  
Дата: 20.01.20 13:03
Оценка:
Здраствуйте.

Имеется сл. код на шарпе:

private static async void Test()
{
            var tcs = new TaskCompletionSource<int>();
            var task = Task.Factory.StartNew(() =>
            {
                Thread.Sleep(1000);
                tcs.SetResult(10);
                Console.WriteLine("here3");
            });

            await tcs.Task; //1
            task.Wait(); //1
            //await tcs.Task.ContinueWith(t => task.Wait());//2


            Console.WriteLine("here1");
}


Данный код виснет, т.к. task.Wait() tcs.SetResult() оказываются на одном стеке (соотв. в одном потоке).
Более того, никакие опции типа TaskCreationOptions.LongRunning | TaskCreationOptions.RunContinuationsAsynchronously не помогают.
Почему так? Т.е. почему все оказалось за инлайнено? Если закомментировать //1 и раскомментировать //2 то все ожидаемо работает...
Есть какая-то спецификация на это поведение, как глядя на данный код можно понять, что здесь deadlock?

Заранее благодарю.
Кодом людям нужно помогать!
Re: Почему будет deadlock?
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 20.01.20 13:25
Оценка:
Здравствуйте, Sharov, Вы писали:

Попробуй ConfigureAwait(false)
https://habr.com/ru/company/clrium/blog/463587/
и солнце б утром не вставало, когда бы не было меня
Re: Почему будет deadlock?
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 20.01.20 13:31
Оценка:
Здравствуйте, Sharov, Вы писали:

S>Здраствуйте.


S>Имеется сл. код на шарпе:


S>
S>private static async void Test()
S>{
S>            var tcs = new TaskCompletionSource<int>();
S>            var task = Task.Factory.StartNew(() =>
S>            {
S>                Thread.Sleep(1000);
S>                tcs.SetResult(10);
S>                Console.WriteLine("here3");
S>            });

S>            await tcs.Task; //1
S>            task.Wait(); //1
S>            //await tcs.Task.ContinueWith(t => task.Wait());//2


S>            Console.WriteLine("here1");
S>}
S>


Проблема в том, что продолжение tcs.SetResult(10) будет в том же потоке, что и task.
Можно продолжить в отдельном таске и на всякий случай await tcs.Task.ConfigureAwait(false).

var task = Task.Factory.StartNew(() =>
            {
                Thread.Sleep(1000);
                Task.Factory.StartNew(() => tcs.SetResult(10));
                Console.WriteLine("here3");
            });
и солнце б утром не вставало, когда бы не было меня
Re[2]: Почему будет deadlock?
От: Sharov Россия  
Дата: 20.01.20 13:41
Оценка:
Здравствуйте, Serginio1, Вы писали:

S>Здравствуйте, Sharov, Вы писали:


S>Попробуй ConfigureAwait(false)

S>https://habr.com/ru/company/clrium/blog/463587/

1) почему это должно помочь, Sync. context тут совершенно не критичен?
2) добавил await tcs.Task.ConfigureAwait(false); -- ничего не поменялось
Кодом людям нужно помогать!
Re[2]: Почему будет deadlock?
От: Sharov Россия  
Дата: 20.01.20 13:43
Оценка:
Здравствуйте, Serginio1, Вы писали:



S> Проблема в том, что продолжение tcs.SetResult(10) будет в том же потоке, что и task.

S>Можно продолжить в отдельном таске и на всякий случай await tcs.Task.ConfigureAwait(false).

Почему такое неочевидное поведение?

S>
S>var task = Task.Factory.StartNew(() =>
S>            {
S>                Thread.Sleep(1000);
S>                Task.Factory.StartNew(() => tcs.SetResult(10));
S>                Console.WriteLine("here3");
S>            });
S>


М-да...
Кодом людям нужно помогать!
Re: Почему будет deadlock?
От: alexander_r  
Дата: 20.01.20 14:22
Оценка:
Здравствуйте, Sharov, Вы писали:

просто await task;

private static async void Test()
{
    var tcs = new TaskCompletionSource<int>();
    var task = Task.Factory.StartNew(() =>
    {
        Thread.Sleep(1000);
                tcs.SetResult(10);
                Console.WriteLine("here3");
    });

    await tcs.Task; //1
    await task;

    //task.Wait(); //1
    //await tcs.Task.ContinueWith(t => task.Wait());//2


    Console.WriteLine("here1");
}

ну или .ContinueWith(t => task.Wait())
Отредактировано 20.01.2020 14:33 alexander_r . Предыдущая версия .
Re[2]: Почему будет deadlock?
От: Sharov Россия  
Дата: 20.01.20 14:32
Оценка:
Здравствуйте, alexander_r, Вы писали:

_>просто await task;

_>ну или .ContinueWith(t => task.Wait())

Это понятно, но почему в исходном случае deadlock? Почему все на одном стеке? Как это можно понять не запуская код, т.к. данная ситуация стабильно вопроизводится, следовательно есть
какая-то неявная особенность.
Кодом людям нужно помогать!
Re[3]: Почему будет deadlock?
От: alexander_r  
Дата: 20.01.20 15:22
Оценка:
Здравствуйте, 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

попробуйте добавить в ваш код
    Form f = new Form();
    Test();
    Application.Run(f);
Отредактировано 20.01.2020 15:34 alexander_r . Предыдущая версия .
Re[3]: Почему будет deadlock?
От: RushDevion Россия  
Дата: 20.01.20 15:26
Оценка: 122 (6)
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 будет запускаться на другом потоке и дедлока не будет
Отредактировано 20.01.2020 15:32 RushDevion . Предыдущая версия .
Re[4]: Почему будет deadlock?
От: Sharov Россия  
Дата: 20.01.20 15:39
Оценка:
Здравствуйте, 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() --
Кодом людям нужно помогать!
Re[4]: Почему будет deadlock?
От: Sharov Россия  
Дата: 20.01.20 16:12
Оценка:
Здравствуйте, 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() будет еще вызов продолжения, которое меня и подвело...
Кодом людям нужно помогать!
Re[5]: Почему будет deadlock?
От: Sinclair Россия https://github.com/evilguest/
Дата: 21.01.20 02:49
Оценка:
Здравствуйте, Sharov, Вы писали:

S>Круто -- . Но вот как это все можно понять просто глядя на код? Да, если иметь подобный опыт выстрелов себе в ногу, то возможно такое поймать. А если где-то написал такое, выглядит более-менее нормально,

S>но это какой-то сильно нечасто вызываемый фрагмент кода, тестов особых нет и т.д. Никаких предупреждений от компилятора, R#, ничего. А меж тем куча неявных вещей проиходит, куча...
S>В целом, я не заметил\не знал\забыл, что после SetResult() будет еще вызов продолжения, которое меня и подвело...
Мне кажется, что TPL разработана под какие-то более простые сценарии работы.
Т.е. если хочется прямо вручную контролировать, что в каком потоке — то проще и надёжнее по-старинке, с запуском тредов.
А все эти await и async рассчитаны на то, что ты не будешь вручную делать штуки типа SetResult и внутри таски обращаться к другим таскам. Потому что очень быстро можно потеряться, кто от кого зависит, и нет ли скрытых ожиданий на самих себе.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[6]: Почему будет deadlock?
От: Jack128  
Дата: 21.01.20 05:42
Оценка: 5 (1) +3
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, Sharov, Вы писали:


S>А все эти await и async рассчитаны на то, что ты не будешь вручную делать штуки типа SetResult и внутри таски обращаться к другим таскам. Потому что очень быстро можно потеряться, кто от кого зависит, и нет ли скрытых ожиданий на самих себе.


Дело не SetResult, а конкретно в Task.Wait()/Task.GetResult(). Хотите, чтоб не было дедлоков — забудьте про эти методы
Re[6]: Почему будет deadlock?
От: Sharov Россия  
Дата: 21.01.20 09:09
Оценка:
Здравствуйте, Sinclair, Вы писали:


S>Мне кажется, что TPL разработана под какие-то более простые сценарии работы.

S>Т.е. если хочется прямо вручную контролировать, что в каком потоке — то проще и надёжнее по-старинке, с запуском тредов.
S>А все эти await и async рассчитаны на то, что ты не будешь вручную делать штуки типа SetResult и внутри таски обращаться к другим таскам. Потому что очень быстро можно потеряться, кто от кого зависит, и нет ли скрытых ожиданий на самих себе.

Удивляет почему нету никаких подсказок от студии, мол вот это таска, а это ее продолжение. И как бы они чем-то одинаковым были выделены.
Кодом людям нужно помогать!
Re[4]: Почему будет deadlock?
От: fmiracle  
Дата: 21.01.20 09:13
Оценка: +1
Здравствуйте, RushDevion, Вы писали:

RD>// Если написать

RD>var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
RD>// То continuation будет запускаться на другом потоке и дедлока не будет

Точно? По логике должно было быть так, но я пробовал по данному примеру так делать — и оно все равно запускалось в том же потоке. Что меня весьма удивило.
Re[5]: Почему будет deadlock?
От: RushDevion Россия  
Дата: 21.01.20 09:52
Оценка: 8 (2)
F>Точно? По логике должно было быть так, но я пробовал по данному примеру так делать — и оно все равно запускалось в том же потоке. Что меня весьма удивило.

Пожалуй, не совсем корректно выразился.
Суть в том, что continuation именно асинхронно будет выполняться (а не сразу же, как parent-таска завершится), а зашедулится оно может и на том же потоке.
Вот рекомендую почитать, если есть желание более детально разобраться.
Re[5]: Почему будет deadlock?
От: Sharov Россия  
Дата: 21.01.20 10:03
Оценка:
Здравствуйте, fmiracle, Вы писали:

F>Здравствуйте, RushDevion, Вы писали:


RD>>// Если написать

RD>>var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
RD>>// То continuation будет запускаться на другом потоке и дедлока не будет

F>Точно? По логике должно было быть так, но я пробовал по данному примеру так делать — и оно все равно запускалось в том же потоке. Что меня весьма удивило.


Т.е. опять же был deadlock?
Кодом людям нужно помогать!
Re[6]: Почему будет deadlock?
От: fmiracle  
Дата: 21.01.20 10:27
Оценка:
Здравствуйте, Sharov, Вы писали:

RD>>>// Если написать

RD>>>var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
RD>>>// То continuation будет запускаться на другом потоке и дедлока не будет
F>>Точно? По логике должно было быть так, но я пробовал по данному примеру так делать — и оно все равно запускалось в том же потоке. Что меня весьма удивило.
S>Т.е. опять же был deadlock?

угу.

Но я подозреваю что это какой-то баг типа этого.
Re[6]: Почему будет deadlock?
От: fmiracle  
Дата: 21.01.20 10:49
Оценка:
Здравствуйте, RushDevion, Вы писали:

F>>Точно? По логике должно было быть так, но я пробовал по данному примеру так делать — и оно все равно запускалось в том же потоке. Что меня весьма удивило.

RD>Пожалуй, не совсем корректно выразился.
RD>Суть в том, что continuation именно асинхронно будет выполняться (а не сразу же, как parent-таска завершится), а зашедулится оно может и на том же потоке.
RD>Вот рекомендую почитать, если есть желание более детально разобраться.

Я неправильно сказал видимо. Проблема была в том, что независимо от того ставилось RunContinuationsAsynchronously или нет, после SetResult всегда вызывалось продолжение (1) (которое ставит task.Wait) в том же потоке — и только после него продолжалось выполнение кода дальше после SetResult и тоже в том же потоке — итого дедлок.
Хотя по логике получалось что должен был завершиться код который после SetResult и отдельно запланироваться запуск продолжения.

или что-то у меня было не так?
код:
private static void Main( string[] args )
        {
            Test();
            Thread.Sleep( 3500 );
        }

        private static async void Test()
        {
            Console.WriteLine( "T{0} run test", Thread.CurrentThread.ManagedThreadId );
            var tcs = new TaskCompletionSource<int>( TaskContinuationOptions.RunContinuationsAsynchronously );
            var task = Task.Factory.StartNew( () =>
            {
                Console.WriteLine( "T{0} processing task..", Thread.CurrentThread.ManagedThreadId );
                Thread.Sleep( 100 );
                tcs.SetResult( 10 );
                Console.WriteLine( "T{0} task done", Thread.CurrentThread.ManagedThreadId );

            });
            Console.WriteLine( "T{0} task started, awaiting", Thread.CurrentThread.ManagedThreadId );

            await tcs.Task; 
            Console.WriteLine( "T{0} cont, before Wait", Thread.CurrentThread.ManagedThreadId );
            task.Wait(); 
            Console.WriteLine( "T{0} cont, after Wait", Thread.CurrentThread.ManagedThreadId );
        }


вывод:
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 а может и нет. Но главное что если поток один, то последовательность операций такая.
А оно не так.
Где я неправ?

Или это все же баг типа такого?
Re[7]: Почему будет deadlock?
От: RushDevion Россия  
Дата: 21.01.20 11:14
Оценка: 138 (4)
F>private static void Main( string[] args )
F>        {
F>            Test();
F>            Thread.Sleep( 3500 );
F>        }

F>        private static async void Test()
F>        {
F>            // Тут у тебя вызывается перегрузка 
F>            // public TaskCompletionSource(object state), т.е. new TaskCompletionSource<int>((object)TaskContinuationOptions.RunContinuationsAsynchronously)
F>            // нужно TaskCreationOptions использовать, а не TaskContinuationOptions. :)
F>            var tcs = new TaskCompletionSource<int>(TaskContinuationOptions.RunContinuationsAsynchronously); 
F>        }
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.