Странное поведение async/await в WinForms

254
28 января 2020, 13:10

Разбираюсь в async/await. Вот такой код в WinForms (чтобы был UI-контекст):

        private async void Parent(){
            Write("Parent() start.");
            var context = SynchronizationContext.Current;
            Task task = Task.Run(()=>ChildAsync(context));
            while (!task.IsCompleted)
            {
                Write("loop");
                await Task.Delay(1000);
            }
            Write("Parent() done.");
        }
        private async Task ChildAsync(SynchronizationContext context)
        { 
            await Task.Delay(3000);
        }
        public void Write(string message){
            richTextBox1.AppendText($"\n{message}");
        }

Почему в коде метода ChildAsync вызов функции Write() мешает дальнейшему выполнение кода, но при этом не мешает задаче завершиться (ведь сообщение Parent() done. появляется):

При этом если Write() вызвать после await, то это не мешает выполнению задачи:

И еще меня сильно смущает, что программа выводит сообщение "loop" хотя бы один раз - если Write() вызывает эксепшен и завершает задачу, то почему программа все же один раз входит в цикл?

Изменение задержки не помогает:

        private async void Parent(){
            Write("Parent() start.");
            var context = SynchronizationContext.Current;
            Task task = Task.Run(() => ChildAsync(context));
            while (!task.IsCompleted)
            {
                await Task.Delay(5000);
                Write("loop");
            }
            Write("Parent() done.");
        }
        private async Task ChildAsync(SynchronizationContext context)
        {
            Write(".......ChildAsync() done.");
            context.Post((x) => Write(".......ChildAsync() done"), null);
            await Task.Delay(3000);
        }

Дополнение - отлов ошибок из другого потока:

Заключил код по созданию task'a в try-catch, но он не ловит ошибку. Почему?

       private async void Parent(){
            Write("Parent() start.");
            var context = SynchronizationContext.Current;
            Task task = null;
            try
            {
                task = Task.Run(() => ChildAsync(context));
            }
            catch (Exception e)
            {
                MessageBox.Show(task.Exception.InnerException.Message);
            }
            while (!task.IsCompleted)
            {
                await Task.Delay(1000);
                Write("loop");
            }
            Write("Parent() done.");
        }
Answer 1

Вы, наверное, думаете, что эксепшон в таске должен выкидывать вас из цикла. Нет, тот эксепшон остается в таске (если вы не делаете await mytask), то есть раз в основном потоке вы успели зайти в цикл, то код в цикле выполнится, хоть час ждите. Вот если вы await Task.Delay(5000); поставите перед циклом while, а не внутри цикла, то тогда да, в цикл не попадете, за 5 сек таск успеет упасть

READ ALSO
Как передать события с окна в WPF на первое окно, находящееся под ним?

Как передать события с окна в WPF на первое окно, находящееся под ним?

Нужно создать окно для эффекта водяного знака на экранеВывести его поверх других окон и задать прозрачность получилось, но все события клика...

151
Как получить текущий сертификат

Как получить текущий сертификат

Настроил IIS на аутентификацию по смарт-карте по данной статье https://blogjayway

176
Перемешать значения в массиве

Перемешать значения в массиве

Нужно, чтобы значения из массива всегда выводились в случайном порядкеПробовал array_rand, но эта функция перемешивает только ключи массива

145