Оператор async / await не работает правильно в AspNet-MVC, но работает в консольном приложении - PullRequest
0 голосов
/ 08 апреля 2019

Справочная информация: у меня длинный веб-запрос linq к базе данных. Поэтому я хочу показать пользователю запрос-таймер. Я пытался сделать это с помощью операторов async / await, но я застрял. (Я использую signalR для режима реального времени)

Мир моего тест-кода:

MyHub.cs

public class MyHub : Hub
    {

        public void Message(string message)
        {
            Clients.All.message(message);
        }

        public void Tick(int value)
        {
            Thread.Sleep(1000);
            Clients.Caller.tick(value);
        }

        public void StartAsyncMethod(int value)
        {
            Clients.All.message("<br> M1: StartAsyncMethod started!");
            int t = 0;
            MyAsyncClass myAsyncClass = new MyAsyncClass();
            Task task = myAsyncClass.AsyncMethod(value*1000);
            Clients.All.message("<br> M1: Start While...");

            while (!task.IsCompleted)
            {
                t++;
                Tick(t);
            }

            Clients.All.message("<br> M1: StartAsyncMethod Finished!");

        }
    }

MyAsyncClass.cs

    public class MyAsyncClass
    {
        public async Task AsyncMethod(int _i)
        {
            var hub = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
            hub.Clients.All.message($"<br>==M2== Wait for {_i} milliseconds");
            await Task.Run(() => Sleep(_i));
            hub.Clients.All.message($"<br>==M2== Finish inner method...");
        }

        private void Sleep(int value)
        {
            Thread.Sleep(value);
        }
    }

И дело в while (!task.IsCompleted) - приложение складывается в это состояние:

app state whith while-statement

Если прокомментировать while, он работает нормально (но без таймера, конечно):

app state without while-statement

Более того - в простом консольном приложении этот код работает в обоих вариантах!

Код:

class Program
    {
        static void Main(string[] args)
        {
            int i = 5;
            Console.WriteLine($"Main : Start StartAsyncMethod({i})");
            StartAsyncMethod(i);
            Console.WriteLine($"Main : FINISH StartAsyncMethod({i})");
            Console.WriteLine("\n\nPress any key to exit....");
            Console.ReadKey();
        }

        public static void StartAsyncMethod(int value)
        {
            Console.WriteLine("  M1 : StartAsyncMethod started!");
            int timer = 0;
            MyAsyncClass myAsyncClass = new MyAsyncClass();
            Task task = myAsyncClass.AsyncMethod(value * 1000);
            //while (!task.IsCompleted)
            //{
            //    timer++;
            //    Thread.Sleep(1000);
            //    Console.WriteLine("  M1 : \"hello\" from While cicle ... ");
            //}
            Console.WriteLine("  M1 : StartAsyncMethod FINISHED!");
        }
    }

    public class MyAsyncClass
    {
        public async Task AsyncMethod(int _i)
        {
            Console.WriteLine($"    M2 : Wait for {_i} milliseconds");
            await Task.Run(() => Sleep(_i));
            Console.WriteLine($"    M2 : Finish inner method...");
        }

        private void Sleep(int v)
        {
            Console.WriteLine($"    M2 : Sleep for {v} mls");
            Thread.Sleep(v);
        }
    }

Выходы:

С while:

enter image description here

Без while:

enter image description here

Вопрос: действительно ли этот код по-разному работает в ASPnet и ConsoleApp, или я что-то пропустил?

Спасибо.

Ответы [ 2 ]

2 голосов
/ 08 апреля 2019

Это потому, что вы неправильно используете async/await.Обратите внимание, что у вас есть 2 задачи: одна возвращается Task.Run, а другая - AsyncMethod.Зацикливаясь на верхнем уровне Task, вы блокируете поток запроса и продолжение Task.Run, которое

hub.Clients.All.message($"<br>==M2== Finish inner method...");

, не может быть выполнено в ASP.NET, поскольку оно использует контекст синхронизации, который в случае, если ASP.NET обеспечивает выполнение не более одного потока за раз для потоков, которые совместно используют контекст синхронизации (поток запросов и потоки продолжения совместно используют контекст).Таким образом, второе задание также не может быть завершено.Консоль не использует контекст синхронизации, поэтому ваши продолжения запланированы в пуле потоков.Вам нужно либо использовать ConfigureAwait(flase) для планирования ваших асинхронных задач, либо делать все асинхронно (что более правильно).Вы можете увидеть пример здесь о том, как реализовать прогресс для async задач.

Обновление : для придания некоторой ясности цели контекста синхронизации.Давайте представим, что типичная лебедка асинхронного потока запускается асинхронным событием (пользовательский ввод, входящий запрос и т. Д.) И завершается некоторым асинхронным действием (передача данных во внешний источник данных, файл, извлечение данных из веб-ресурса и т. Д.),Асинхронное приложение имеет дело со многими из таких потоков, которые из-за их асинхронной природы могут запускаться одновременно и могут завершаться одновременно, и этот факт среди преимуществ налагает некоторые подводные камни.Например, если поток вызывает несколько асинхронных операций, параллелизм их продолжений может быть проблемой.Решением является синхронизация продолжений - вот где контекст синхронизации вступает в игру.Таким образом, в целом он представляет собой некоторую абстракцию для планирования упорядоченного выполнения с помощью двух методов «Отправить» и «Пост», которые имеют семантику «вызвать и ждать» и «запустить и забыть» соответственно.Почему не просто примитивы синхронизации?Вкратце контекст синхронизации обеспечивает более общий подход в виде паттерна полусинхронизация / полусинхронизация.Часто ресурсы, используемые для обслуживания асинхронных продолжений, довольно дороги (например, в ОС семейства Windows механизм портов завершения ввода-вывода подразумевает использование специального пула потоков для обслуживания завершенных запросов ввода-вывода), и настоятельно рекомендуется не занимать такие ресурсы дольше, чемнеобходимый период времени, поэтому подход «запустить и забыть» часто является предпочтительным способом вместо ожидания объекта синхронизации и блокировки потока, который будет обслуживать другие асинхронные продолжения, а контекст синхронизации обеспечивает абстракцию, которая позволяет эффективно использовать базовую инфраструктуру.В качестве побочного эффекта некоторых реализаций контекста синхронизации можно выделить возможность делегировать выполнение некоторого кода из одного потока в другой конкретный поток (как это делают контексты синхронизации WinForms или WPF), но я бы сказал, что это скорее зависит от реализации.

1 голос
/ 08 апреля 2019

В дополнение к ответу @Dimtro Mukalov я постараюсь ответить на ваш вопрос:

этот код действительно работает по-разному в ASPnet и ConsoleApp, или я что-то пропустил?

Да, определенно!

Подумайте об этом: веб-клиент обновляется только за один раз через веб-запросы.Он косвенно связан с потоками на сервере.

В консольном приложении выходные сообщения генерируются немедленно на консоли, а на ASP.net MVC результаты / сообщения собираются в Await (зависит от времени).) мода, а затем отправляется обратно клиенту.

Короче говоря, шаблон Coding TAP в ASP.net не транслируется напрямую из консольных приложений.Для более подробного объяснения, пожалуйста, прочитайте:

Асинхронное программирование: введение в Async / Await на ASP.NET

...