Тупик при использовании async/await

Я пытаюсь понять await и async.

Это работает очень хорошо. Но сейчас у меня тупик.

Я вызвал ConfigureAwait с помощью false, как в эту статью, но мой код по-прежнему блокируется.

Вот небольшой фрагмент моего кода:

private void button1_Click(object sender, EventArgs e)
{
    var result = HeavyWorkAsync().Result;
    richTextBox1.AppendText(result);
}

private string HeavyWork()
{
    for (var index = 0; index < 1000; index++)
    {
        Task.Delay(10).Wait();
    }

    return "finished";
}

private async Task<string> HeavyWorkAsync()
{
    var task = await Task.Factory.StartNew<string>(HeavyWork).ConfigureAwait(false);
    return task;
}

person Marcel Hoffmann    schedule 24.03.2015    source источник
comment
можешь объяснить, почему ты не хочешь делать await HeavyWorkAsync() ?   -  person thumbmunkeys    schedule 24.03.2015
comment
По-моему, некрасиво. Асинхронность в обработчике нажатия кнопки?   -  person Marcel Hoffmann    schedule 24.03.2015
comment
Это рекомендуемый способ обработки этого сценария, и, кроме того, он не блокирует и не блокирует :)   -  person thumbmunkeys    schedule 24.03.2015
comment
Наверху? Что это еще делает, если это не тупик, как в статье?   -  person Marcel Hoffmann    schedule 24.03.2015
comment
async void является законным именно для такого случая. Да, async void уродлив, и его следует избегать в каждой подписи, которая не является подпиской на событие, но у нас нет выбора, потому что используемая нами структура пользовательского интерфейса не поддерживает надлежащие асинхронные обратные вызовы.   -  person Falanwe    schedule 24.03.2015
comment
@MarcelHoffmann ваш код не заблокируется, код в статье работает в этом отношении. Хотя он по-прежнему будет блокировать пользовательский интерфейс на 10 секунд из-за обработки var result = HeavyWorkAsync().Result; в потоке пользовательского интерфейса.   -  person thumbmunkeys    schedule 24.03.2015
comment
Хорошо, спасибо за помощь.   -  person Marcel Hoffmann    schedule 24.03.2015


Ответы (2)


Блокирует не сама задача, а вызов Result. Task представляет собой асинхронную операцию, но вызов ее свойства Result или вызов Wait() заблокирует текущий поток до тех пор, пока метод не вернется. И во многих случаях это приведет к взаимоблокировке, потому что задача не может быть завершена с заблокированным вызывающим потоком!

Чтобы этого не произошло, свяжите задачи асинхронно, используя async и await.

private async void button1_Click(object sender, EventArgs e)
{
    var result = await HeavyWorkAsync(); // <=== await
    richTextBox1.AppendText(result);
}

Кроме того, Task.Delay(10).Wait(); полностью противоречит цели использования задач в первую очередь: это заблокирует текущий поток. Если это действительно то, что вы хотите сделать (а это довольно маловероятно), позвоните Thread.Sleep(10);вместо этого, это сделает ваши намерения более ясными, и у вас будет меньше препятствий, через которые нужно прыгать. Или лучше используйте await Task.Delay(10);in асинхронный метод.

О ConfigureAwait

Что именно делает ConfigureAwait(false)?

Это устраняет обязательство продолжения выполнения задачи в том же контексте, что и вызывающая сторона задачи. В большинстве случаев это означает, что выполнение продолжения в том же контексте больше не гарантируется. Итак, если у меня есть метод, который делает Foo(), немного ждет, а затем Bar(), как этот:

async Task DoStufAsync()
{
    Foo();
    await Task.Delay(10);
    Bar(); // run in the same context as Foo()
}

Я гарантирую, что Bar будет работать в том же контексте. Если бы у меня было ConfigureAwait(false), это уже не так

async Task DoStufAsync()
{
    Foo();
    await Task.Delay(10).ConfigureAwait(false);
    Bar(); // can run on another thread as Foo()
}

Когда вы используете ConfigureAwait(false), вы говорите своей программе, что не возражаете против контекста. Это может решить некоторые проблемы взаимоблокировки, но обычно не является правильным решением. Вероятнее всего, правильное решение никогда не будет ждать задачи блокирующим образом и будет полностью асинхронным.

person Falanwe    schedule 24.03.2015
comment
Обратите внимание, что статья / OP пытается использовать ConfigureAwait, чтобы в первую очередь не создавать обработчик кликов async. - person James Thorpe; 24.03.2015
comment
Проблема в том, что ConfigureAwait этого не делает: поток пользовательского интерфейса перестанет отвечать из-за вызова Result независимо от внутренней работы методов. Лучшее, на что он может надеяться, — это огромное уродливое замораживание, которое в конечном итоге завершится, а не бесконечный тупик. - person Falanwe; 24.03.2015
comment
Не могли бы вы объяснить это в своем ответе, чтобы завершить его, так как это основной вопрос - я сам довольно новичок в асинхронных вещах, но кажется, что статья подразумевает, что ее можно использовать таким образом - person James Thorpe; 24.03.2015
comment
@JamesThorpe: Я, конечно, могу и сделаю это прямо сейчас! - person Falanwe; 24.03.2015
comment
Хорошо, я думаю, что понял это. Если я использую этот код в потоке, отличном от пользовательского интерфейса, мой код должен работать, верно? - person Marcel Hoffmann; 24.03.2015
comment
@MarcelHoffmann: да, это сработает, но вы все равно будете без необходимости блокировать поток. Темы — ценный ресурс, не тратьте их понапрасну. - person Falanwe; 24.03.2015
comment
Хорошо, спасибо за ответ. Лучший способ, например, вызвать событие, верно? Это решение не блокирует. - person Marcel Hoffmann; 24.03.2015
comment
await не возобновляется в том же потоке; он возобновляется в том же контексте. Контекст пользовательского интерфейса имеет отношение один к одному с потоком, а другие контексты (например, ASP.NET, пул потоков и т. д.) — нет. - person Stephen Cleary; 24.03.2015

Чтобы расширить ответ Фаланве, вам следует посетить блог Стивена Клири. сообщение. Основываясь на коде, я предполагаю, что вы используете приложение Windows Forms, поэтому вызов Task.Result будет выполнять задачу в контексте пользовательского интерфейса, что, в свою очередь, блокирует поток пользовательского интерфейса.

person mjk5182    schedule 24.03.2015