Arhn - архитектура программирования

Неожиданное поведение после возврата из ожидания

Я знаю, что есть много вопросов об async/await, но я не смог найти на них ответа.

Я столкнулся с чем-то, чего не понимаю, рассмотрите следующий код:

void Main()
{
    Poetry();
    while (true)
    {
        Console.WriteLine("Outside, within Main.");
        Thread.Sleep(200);
    }
}

async void Poetry()
{
   //.. stuff happens before await
   await Task.Delay(10);
   for (int i = 0; i < 10; i++)
   {
       Console.WriteLine("Inside, after await.");
       Thread.Sleep(200);
   }
}

Очевидно, что в операторе await управление возвращается вызывающей стороне, а ожидаемый метод выполняется в фоновом режиме. (предположим, операция ввода-вывода)

Но после того, как управление возвращается к оператору await, выполнение становится параллельным, вместо того, чтобы (мое ожидание) оставаться однопоточным.

Я ожидаю, что после того, как «Задержка» будет завершена, нить будет принудительно возвращена в метод Поэзии, продолжится с того места, где она была.

Что он и делает. Странная вещь для меня, почему метод «Основной» продолжает работать? это одна нить перескакивает с одной на другую? или есть два параллельных потока?

Разве это не проблема безопасности потоков, еще раз?

Я нахожу это запутанным. Я не эксперт. Спасибо.


  • Я также предлагаю прочитать это.... msdn.microsoft.com/en-us /magazine/jj991977.aspx 08.12.2013
  • В приложении консольного режима нет механизма для запуска продолжения в том же потоке. Для этого требуется поставщик синхронизации, у вас его нет. Приложение с графическим интерфейсом имеет одно, оно прокачивает цикл сообщений. Самый очевидный способ увидеть, как это не может работать, — посмотреть, что делает ваш основной поток. Оно спит. Вы не можете выполнять код, когда спите. 08.12.2013

Ответы:


1

В моем блоге есть описание того, как async методы возобновляют работу после await. По сути, await захватывает текущий SynchronizationContext, если это не null, и в этом случае он захватывает текущий TaskScheduler. Затем этот «контекст» используется для планирования оставшейся части метода.

Поскольку вы выполняете консольное приложение, SynchronizationContext нет, а TaskScheduler по умолчанию фиксируется для выполнения оставшейся части метода async. Этот контекст ставит в очередь метод async в пул потоков. Невозможно вернуться к основному потоку консольного приложения, если вы фактически не зададите ему основной цикл с SynchronizationContext (или TaskScheduler), который ставится в очередь к этому основному циклу.

08.12.2013
  • Я добавил свой собственный ответ. Я хотел бы услышать, что вы думаете. 11.12.2013

  • 2

    Прочитайте Все о контексте синхронизации, и я уверен, стать менее запутанным. Поведение, которое вы видите, имеет смысл. Task.Delay использует внутренние API Win32 Kernel Timer (а именно, CreateTimerQueueTimer). Обратный вызов таймера вызывается в потоке пула, отличном от вашего потока Main. Вот где остальная часть Poetry после await продолжает выполняться. Так работает планировщик задач по умолчанию при отсутствии контекста синхронизации в исходном потоке, инициировавшем await.

    Поскольку вы не выполняете await задачу Poetry() (и вы не можете этого сделать, пока не вернете Task вместо void), его цикл for продолжает выполняться параллельно с циклом while в вашем Main. Почему и, что более важно, как вы ожидаете, что он будет "принудительно" возвращен в поток Main? Для этого должна быть какая-то явная точка синхронизации, поток не может просто прерваться в середине цикла while.

    В приложении пользовательского интерфейса основной цикл обработки сообщений может служить точкой синхронизации такого рода. Например. для приложения WinForms WindowsFormsSynchronizationContext сделает это возможным. Если await Task.Delay() вызывается в основном потоке пользовательского интерфейса, код после await будет асинхронно продолжаться в основном потоке пользовательского интерфейса после некоторой будущей итерации цикла сообщений, выполняемого Application.Run.

    Таким образом, если бы это был поток пользовательского интерфейса, остальная часть Poetry не выполнялась бы параллельно с циклом while, следующим за вызовом Poetry(). Скорее, он будет выполнен, когда поток управления вернется в цикл обработки сообщений. Или вы можете явно перекачивать сообщения с Application.DoEvents() для продолжения, хотя я бы не рекомендовал этого делать.

    Кстати, не используйте async void, а используйте async Task, подробнее< /а>.

    08.12.2013

    3

    Когда вы вызываете асинхронную подпрограмму, цель этого состоит в том, чтобы позволить программе запускать метод, в то же время позволяя вызывающей подпрограмме, форме или приложению продолжать реагировать на пользовательский ввод (другими словами, продолжать выполнение в обычном режиме). Ключевое слово «ожидание» приостанавливает выполнение в точке его использования, запускает задачу с использованием другого потока, а затем возвращается к этой строке после завершения потока.

    Итак, в вашем случае, если вы хотите, чтобы основная процедура приостанавливалась до тех пор, пока не будет выполнена процедура «Поэзия», вам нужно использовать ключевое слово await примерно так:

    void async Main()
    {
       await Poetry();
       while (true)
       {
           Console.WriteLine("Outside, within Main.");
          Thread.Sleep(200);
       }
    }
    

    Вам также потребуется изменить определение Poetry, чтобы разрешить использование ключевого слова await:

    async Task Poetry()
    

    Поскольку этот вопрос действительно меня заинтриговал, я пошел дальше и написал пример программы, которую вы действительно можете скомпилировать. Просто создайте новое консольное приложение и вставьте в него этот пример. Вы можете увидеть результат использования «ожидания» по сравнению с его отсутствием.

    class Program
    {
        static void Main(string[] args)
        {
            RunMain();
    
            // pause long enough for all async routines to complete (10 minutes)
            System.Threading.Thread.Sleep(10 * 60 * 1000);
        }
    
        private static async void RunMain()
        {
            // with await this will pause for poetry
            await Poetry();
    
            // without await this just runs
            // Poetry();
    
            for (int main = 0; main < 25; main++)
            {
                System.Threading.Thread.Sleep(10);
                Console.WriteLine("MAIN [" + main + "]");
            }
        }
    
        private static async Task Poetry()
        {
            await Task.Delay(10);
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("IN THE POETRY ROUTINE [" + i + "]");
                System.Threading.Thread.Sleep(10);
            }
        }
    }
    

    Удачного тестирования! О, и вы по-прежнему можете прочитать дополнительную информацию здесь.

    07.12.2013
  • -1 запускает задачу с использованием другого потока. Задача выполняется в одном потоке. ваш код не скомпилируется, вы не можете ждать метода void 08.12.2013
  • Попробуйте изменить void на Task‹void› Я считаю, что это проблема, почему он не позволяет ждать. Я могу изменить, чтобы показать это. 08.12.2013
  • Задача‹void› не существует. для ожидания метода без возвращаемого значения вы должны вернуть Task 08.12.2013
  • Да, вы правы - надеюсь, теперь ответ будет правильным. Дайте мне знать, если это не сработает. 08.12.2013
  • Я добавил пример, который вы можете скомпилировать и запустить. Я надеюсь, что это тестовое приложение поможет увидеть, что делают ключевые слова await и async. 08.12.2013

  • 4

    Я хотел бы ответить на свой вопрос здесь.

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

    Итак, Task.Delay использует Timer, который использует операционную систему для запуска события через N миллисекунд. после этого периода создается новый объединенный поток, который практически ничего не делает.

    Ключевое слово await означает, что после того, как поток завершится (и он почти ничего не делает), он должен продолжить выполнение всего, что идет после ключевого слова await.

    Здесь идет контекст синхронизации, как упоминалось в других ответах.

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

    Если есть синхронизирующий контекст, вновь созданный пул-поток будет только подталкивать все, что идет после ожидания, в синхронизирующий контекст.

    Ради этого, вот несколько моментов, которые я не понял:

    • Async/await не делает ничего, что раньше было невозможно (технически говоря). Просто, может быть, удивительно неуклюжий.
    • Это просто поддержка языка для некоторых классов .NET 4.5.
    • Это очень похоже на доходность. Он может разбить ваш метод на несколько методов, может даже сгенерировать класс позади него и использовать некоторые методы из BCL, но не более того.

    В любом случае, я рекомендую прочитать главу C# 5.0 In A Nutshell "Параллелизм и асинхронность". Это мне очень помогло. Это здорово, и на самом деле объясняет всю историю.

    11.12.2013
  • Никогда не понимал смысла минусовать без комментария. Явно ли то, что я написал, вводит в заблуждение? Должен ли я удалить его? Это недостаточно актуально? 11.12.2013
  • Новые материалы

    Коллекции публикаций по глубокому обучению
    Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

    Представляем: Pepita
    Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

    Советы по коду Laravel #2
    1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

    Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
    Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

    3 способа решить квадратное уравнение (3-й мой любимый) -
    1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

    Создание VR-миров с A-Frame
    Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

    Демистификация рекурсии
    КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..