очікуємо vs Task.Wait - Тупик?


197

Я не зовсім розумію різницю між Task.Waitі await.

У сервісі ASP.NET WebAPI у мене є щось подібне до таких функцій:

public class TestController : ApiController
{
    public static async Task<string> Foo()
    {
        await Task.Delay(1).ConfigureAwait(false);
        return "";
    }

    public async static Task<string> Bar()
    {
        return await Foo();
    }

    public async static Task<string> Ros()
    {
        return await Bar();
    }

    // GET api/test
    public IEnumerable<string> Get()
    {
        Task.WaitAll(Enumerable.Range(0, 10).Select(x => Ros()).ToArray());

        return new string[] { "value1", "value2" }; // This will never execute
    }
}

Де Getбуде тупик.

Що може спричинити це? Чому це не викликає проблем, коли я використовую блокування очікування, а не await Task.Delay?


@Servy: Я повернусь із репо, як тільки встигну. Наразі це працює, з Task.Delay(1).Wait()чим досить добре.
ronag

2
Task.Delay(1).Wait()в основному точно те саме, що і Thread.Sleep(1000). У фактичному виробничому коді це рідко доцільно.
Сервіс

@ronag: Ваш WaitAllпричиною стає тупик. Дивіться посилання на мій блог у моїй відповіді для отримання більш детальної інформації. Ви повинні використовувати await Task.WhenAllзамість цього.
Стівен Клірі

6
@ronag Тому що у вас є ConfigureAwait(false)в єдиний виклик Barабо Rosне безвихідь, а тому , що у вас є перелічуваних , що створює більше , ніж один , а потім чекати на всіх тих, перша смуга буде тупикової другий. Якщо ви await Task.WhenAllзамість того, щоб чекати всіх завдань, щоб не блокувати контекст ASP, ви побачите, як метод повертається нормально.
Сервіс

2
@ronag Вашим іншим варіантом буде додати .ConfigureAwait(false) всю дорогу вгору по дереву, поки ви не заблокуєте, таким чином нічого не намагається повернутися до основного контексту; це спрацювало б. Іншим варіантом було б розкрутити внутрішній контекст синхронізації. Link . Якщо ви помістите Task.WhenAllв нього, AsyncPump.Runвін ефективно блокує всю справу, не потребуючи ConfigureAwaitнікуди, але це, мабуть, надмірно складне рішення.
Сервіс

Відповіді:


270

Waitі await- хоча подібні концептуально - насправді абсолютно різні.

Waitбуде синхронно блокувати, поки завдання не буде виконано. Тож поточний потік буквально блокується в очікуванні завершення завдання. Як загальне правило, слід використовувати « asyncдо кінця вниз»; тобто не блокуйте asyncкод. У своєму блозі я розглядаю деталі того, як блокування в асинхронному коді викликає тупик .

awaitбуде асинхронно чекати, поки завдання не буде виконано. Це означає, що поточний метод "призупинено" (його стан захоплено), і метод повертає незавершене завдання своєму абоненту. Пізніше, коли awaitвираз закінчується, решта методу планується як продовження.

Ви також згадали про "кооперативний блок", під яким я припускаю, що ви маєте на увазі завдання, яке ви виконуєте, Waitможливо виконати в очікуванні потоку. Бувають ситуації, коли це може статися, але це оптимізація. Існує багато ситуацій, коли це не може статися, наприклад, якщо завдання призначене для іншого планувальника, або якщо воно вже запущене, або якщо це завдання без коду (наприклад, у прикладі вашого коду: Waitне можна виконати Delayзавдання в рядку, оскільки немає коду для нього).

Ви можете знайти мій async/ awaitінтро корисно.


5
Думаю, є непорозуміння, Waitспрацьовує чудові awaitтупики.
ronag

1
Ясно: Так, якщо я замінити await Task.Delay(1)з Task.Delay(1).Wait()сервісом працює нормально, в іншому випадку це глухий кут.
ronag

5
Ні, планувальник завдань цього не зробить. Waitблокує нитку, і її не можна використовувати для інших речей.
Стівен Клірі

8
@ronag Я здогадуюсь, що у вас щойно змішані імена методів, і ваш глухий кут насправді був викликаний блокувальним кодом і працював з awaitкодом. Або це, або тупик не був пов'язаний ні з одним, і ви неправильно діагностували проблему.
Сервіс

3
@hexterminator: Це за дизайном - він чудово підходить для програм інтерфейсу, але, як правило, не заважає додаткам ASP.NET. ASP.NET Core виправила це, видаливши SynchronizationContext, тому блокуючи в запиті ASP.NET Core більше не тупики.
Стівен Клірі

5

На основі прочитаного з різних джерел:

awaitВираз не блокує нитка , на якій він виконується. Натомість він змушує компілятор підписати решту asyncметоду як продовження очікуваного завдання. Потім контроль повертається до абонента asyncметоду. Коли завдання виконано, він викликає його продовження, а виконання asyncметоду поновлюється там, де воно припинено.

Щоб дочекатися завершення синглу task, ви можете викликати його Task.Waitметод. Виклик Waitметоду блокує нитку виклику до тих пір, поки екземпляр одного класу не завершить виконання. Безпараметричний Wait()метод використовується для беззастережного очікування, поки завдання не буде виконано. Завдання імітує роботу, викликаючи Thread.Sleepметод спати протягом двох секунд.

Ця стаття також добре прочитана.


3
"Хіба це технічно невірно? Тоді хтось може уточнити?" - чи можу я уточнити; Ви ставите це як питання? (Я просто хочу зрозуміти, запитуєте ви проти відповіді). Якщо ви запитуєте: це може працювати краще як окреме запитання; навряд чи зберуться тут нові відповіді як відповідь
Марк Гравелл

1
Я відповів на питання і задав окреме питання для сумніву у мене було тут stackoverflow.com/questions/53654006 / ... Спасибі @MarcGravell. Чи можете ви зараз видалити голос за видалення для відповіді?
Аюшмати

"Чи можете ви зараз видалити голосування за видалення для відповіді?" - це не моє; завдяки ♦ будь-яке таке голосування мною мало би діяти негайно. Однак я не думаю, що це дає відповідь на ключові моменти питання, що стосується поведінки в тупиковому стані.
Марк Гравелл

Це не правда. До першого очікування не досягнуто все заблоковано
user1785960

-2

Деякі важливі факти не були наведені в інших відповідях:

"async await" є складнішим на рівні CIL і, таким чином, коштує часу на пам'ять та процесор.

Будь-яке завдання можна скасувати, якщо час очікування неприйнятний.

У випадку, коли "асинхронізація чекає", у нас немає обробника такої задачі, щоб її скасувати чи контролювати.

Використання Завдання є більш гнучким, ніж "асинхронізація чекає".

Будь-яка функція синхронізації може бути завершена асинхронізацією.

public async Task<ActionResult> DoAsync(long id) 
{ 
    return await Task.Run(() => { return DoSync(id); } ); 
} 

"Асинхронізація чекає" породжує багато проблем. Зараз ми не очікуємо, що заява буде досягнута без налагодження часу виконання та налагодження контексту. Якщо спочатку дочекатися не досягнутого, все блокується . Десь навіть очікують, що, здається, все-таки заблоковано:

https://github.com/dotnet/runtime/isissue/36063

Я не бачу, чому я повинен жити з дублюванням коду для методу синхронізації та асинхронізації або з використанням хаків.

Висновок: Створювати завдання вручну та керувати ними набагато краще. Обробник Завдання дає більше контролю. Ми можемо контролювати Завдання та керувати ними:

https://github.com/lsmolinski/MonitoredQueueBackgroundWorkItem

Вибачте за мою англійську.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.