Невозможно указать модификатор «async» в методе «Основной» консольного приложения

Я новичок в асинхронном программировании с модификатором async . Я пытаюсь выяснить, как убедиться, что мой Main метод консольного приложения выполняется асинхронно.

 class Program { static void Main(string[] args) { Bootstrapper bs = new Bootstrapper(); var list = bs.GetList(); } } public class Bootstrapper { public async Task<List> GetList() { GetPrograms pro = new GetPrograms(); return await pro.DownloadTvChannels(); } } 

Я знаю, что это не выполняется асинхронно с «сверху». Поскольку невозможно указать модификатор async для метода Main , как я могу запустить код внутри main асинхронно?

Как вы обнаружили, в VS11 компилятор отключит метод async Main . Это было разрешено (но никогда не рекомендовано) в VS2010 с Async CTP.

У меня есть недавние сообщения в блоге об асинхронных / ожидающих и асинхронных консольных программах в частности. Вот некоторая справочная информация из вступительного сообщения:

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

Позже, когда ожидаемый завершается, он выполнит оставшуюся часть асинхронного метода (в пределах захваченного контекста).

Вот почему это проблема в консольных программах с async Main :

Помните из нашего вступительного сообщения, что метод async вернется к своему вызывающему абоненту до его завершения. Это отлично работает в приложениях пользовательского интерфейса (метод просто возвращается к циклу событий пользовательского интерфейса) и приложений ASP.NET (метод возвращает stream, но сохраняет его в ожидании). Это не так хорошо работает для консольных программ: Main возвращается в ОС – так что ваша программа завершается.

Одним из решений является предоставление вашего собственного контекста – основного цикла для вашей консольной программы, совместимой с асинхронным.

Если у вас есть машина с Async CTP, вы можете использовать метод GeneralThreadAffineContext из теста My Documents \ Microsoft Visual Studio Async CTP \ Samples (C # Testing) Unit Testing \ AsyncTestUtilities . Кроме того, вы можете использовать AsyncContext из моего пакета Nito.AsyncEx NuGet .

Вот пример использования AsyncContext ; GeneralThreadAffineContext практически одинаковое использование:

 using Nito.AsyncEx; class Program { static void Main(string[] args) { AsyncContext.Run(() => MainAsync(args)); } static async void MainAsync(string[] args) { Bootstrapper bs = new Bootstrapper(); var list = await bs.GetList(); } } 

Кроме того, вы можете просто заблокировать основной stream Консоли до завершения асинхронной работы:

 class Program { static void Main(string[] args) { MainAsync(args).GetAwaiter().GetResult(); } static async Task MainAsync(string[] args) { Bootstrapper bs = new Bootstrapper(); var list = await bs.GetList(); } } 

Обратите внимание на использование GetAwaiter().GetResult() ; это позволяет избежать обертывания AggregateException которое происходит, если вы используете Wait() или Result .

Обновление, 2017-11-30: Начиная с версии Visual Studio 2017 Update 3 (15.3), язык теперь поддерживает async Main – если он возвращает Task или Task . Теперь вы можете сделать это:

 class Program { static async Task Main(string[] args) { Bootstrapper bs = new Bootstrapper(); var list = await bs.GetList(); } } 

Семантика выглядит так же, как GetAwaiter().GetResult() для блокировки основного streamа. Однако пока еще нет спецификации языка для C # 7.1, так что это только предположение.

Вы можете решить это с помощью этой простой конструкции:

 class Program { static void Main(string[] args) { Task.Run(async () => { // Do any async anything you need here without worry }).GetAwaiter().GetResult(); } } 

Это поместит все, что вы делаете на ThreadPool, где вы захотите (так что другие Задачи, которые вы запускаете / ожидаете, не пытаетесь воссоединиться с streamом, который им не нужно), и дождитесь, пока все будет сделано до закрытия приложения консоли. Нет необходимости в специальных циклах или внешних библиотеках.

Изменить: включить решение Андрея для неперехваченных исключений.

Вы можете сделать это без внешних библиотек, выполнив следующие действия:

 class Program { static void Main(string[] args) { Bootstrapper bs = new Bootstrapper(); var getListTask = bs.GetList(); // returns the Task> Task.WaitAll(getListTask); // block while the task completes var list = getListTask.Result; } } 

Я добавлю важную особенность, которую игнорируют все остальные ответы: аннулирование.

Одна из больших вещей в TPL – поддержка отмены, а в консольных приложениях используется метод отмены (CTRL + C). Их очень просто связать. Вот как я структурирую все мои консольные приложения async:

 static void Main(string[] args) { CancellationTokenSource cts = new CancellationTokenSource(); System.Console.CancelKeyPress += (s, e) => { e.Cancel = true; cts.Cancel(); }; MainAsync(args, cts.Token).Wait(); } static async Task MainAsync(string[] args, CancellationToken token) { ... } 

В C # 7.1 вы сможете сделать правильный async Main . Соответствующие подписи для метода Main были расширены:

 public static Task Main(); public static Task Main(); public static Task Main(string[] args); public static Task Main(string[] args); 

Например, вы можете делать:

 static async Task Main(string[] args) { Bootstrapper bs = new Bootstrapper(); var list = await bs.GetList(); } 

Во время компиляции метод точки асинхронного ввода будет переведен для вызова GetAwaitor().GetResult() .

Подробности: https://blogs.msdn.microsoft.com/mazhou/2017/05/30/c-7-series-part-2-async-main

РЕДАКТИРОВАТЬ:

Чтобы включить языковые функции C # 7.1, вам нужно щелкнуть правой кнопкой мыши по проекту и нажать «Свойства», затем перейдите на вкладку «Построить». Нажмите кнопку «Дополнительно» внизу:

введите описание изображения здесь

В раскрывающемся меню языковой версии выберите «7.1» (или любое более высокое значение):

введите описание изображения здесь

По умолчанию используется «последняя основная версия», которая будет оценивать (на момент написания этой статьи) версию C # 7.0, которая не поддерживает async main в консольных приложениях.

Этого еще не нужно было, но когда я использовал консольное приложение для быстрых тестов и потребовал async, я просто решил его так:

 class Program { static void Main(string[] args) { MainAsync(args).Wait(); } static async Task MainAsync(string[] args) { // Code here } } 

C # 7.1 (с использованием vs 2017 update 3) вводит asynchronous main

Ты можешь написать:

  static async Task Main(string[] args) { await ... } 

Для более подробной информации C # 7 Series, часть 2: Async Main

Обновить:

Вы можете получить ошибку компиляции:

Программа не содержит статический «Основной» метод, подходящий для точки входа

Эта ошибка вызвана тем, что vs2017.3 настроен по умолчанию как c # 7.0, а не c # 7.1.

Вы должны явно изменить настройку вашего проекта для установки функций c # 7.1.

Вы можете установить c # 7.1 двумя способами:

Способ 1. Использование windows настроек проекта:

  • Откройте настройки своего проекта
  • Выберите вкладку «Сборка»
  • Нажмите кнопку «Дополнительно»
  • Выберите нужную версию. Как показано на следующем рисунке:

введите описание изображения здесь

Метод2: вручную изменить PropertyGroup .csproj

Добавьте эту собственность:

  7.1 

пример:

   AnyCPU true full false bin\Debug\ DEBUG;TRACE prompt 4 false 7.1  

Если вы используете C # 7.1 или более позднюю версию , перейдите к ответу nawfal и просто измените тип возврата основного метода на Task или Task . Если вы не:

  • Имейте async Task MainAsync как сказал Йохан .
  • Вызовите его .GetAwaiter().GetResult() чтобы поймать основное исключение, например do0g .
  • Поддержка отмены, как сказал Кори .
  • Второй CTRL+C должен немедленно прекратить процесс. (Спасибо, бинки !)
  • Handle OperationCancelledException – возвращает соответствующий код ошибки.

Окончательный код выглядит так:

 private static int Main(string[] args) { var cts = new CancellationTokenSource(); Console.CancelKeyPress += (s, e) => { e.Cancel = !cts.IsCancellationRequested; cts.Cancel(); }; try { return MainAsync(args, cts.Token).GetAwaiter().GetResult(); } catch (OperationCanceledException) { return 1223; // Cancelled. } } private static async Task MainAsync(string[] args, CancellationToken cancellationToken) { // Your code... return await Task.FromResult(0); // Success. } 

Для асинхронного вызова задачи из Main используйте

  1. Task.Run () для .NET 4.5

  2. Task.Factory.StartNew () для .NET 4.0 (может потребоваться библиотека Microsoft.Bcl.Async для асинхронных и ожидающих ключевых слов)

Подробности: http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx

В разделе «Главная» попробуйте изменить вызов GetList:

 Task.Run(() => bs.GetList()); 

Когда был введен CTP C # 5, вы, безусловно, могли бы отметить Main с помощью async … хотя, как правило, это не очень хорошая идея. Я считаю, что это было изменено выпуском VS 2013, чтобы стать ошибкой.

Если вы не запустили какие-либо другие streamи переднего плана , ваша программа выйдет, когда Main завершает работу, даже если она запустила некоторые фоновые работы.

Что вы на самом деле пытаетесь сделать? Обратите внимание, что на данный момент ваш GetList() не обязательно должен быть асинхронным – это добавление лишнего слоя без какой-либо реальной причины. Это логически эквивалентно (но сложнее):

 public Task> GetList() { return new GetPrograms().DownloadTvChannels(); } 

В MSDN документация для метода Task.Run (Action) предоставляет этот пример, который показывает, как запустить метод асинхронно из main :

 using System; using System.Threading; using System.Threading.Tasks; public class Example { public static void Main() { ShowThreadInfo("Application"); var t = Task.Run(() => ShowThreadInfo("Task") ); t.Wait(); } static void ShowThreadInfo(String s) { Console.WriteLine("{0} Thread ID: {1}", s, Thread.CurrentThread.ManagedThreadId); } } // The example displays the following output: // Application thread ID: 1 // Task thread ID: 3 

Обратите внимание на это утверждение, которое следует примеру:

Примеры показывают, что асинхронная задача выполняется в другом streamе, чем основной stream приложения.

Итак, если вместо этого вы хотите, чтобы задача выполнялась в основном streamе приложения, см. Ответ @StephenCleary .

И в отношении streamа, на котором выполняется задача, также обратите внимание на комментарий Стивена о его ответе:

Вы можете использовать простой Wait или Result , и нет ничего плохого в этом. Но имейте в виду, что есть два важных отличия: 1) все продолжения async выполняются в пуле streamов, а не в основном streamе, и 2) любые исключения обернуты в исключение AggregateException .

(См. Раздел Обработка исключений (параллельная библиотека задач) для включения обработки исключений для обработки исключения AggregateException .)


Наконец, в MSDN из документации по методу Task.Delay (TimeSpan) в этом примере показано, как запустить асинхронную задачу, которая возвращает значение:

 using System; using System.Threading.Tasks; public class Example { public static void Main() { var t = Task.Run(async delegate { await Task.Delay(TimeSpan.FromSeconds(1.5)); return 42; }); t.Wait(); Console.WriteLine("Task t Status: {0}, Result: {1}", t.Status, t.Result); } } // The example displays the following output: // Task t Status: RanToCompletion, Result: 42 

Обратите внимание, что вместо передачи delegate в Task.Run вы можете передать функцию lambda следующим образом:

 var t = Task.Run(async () => { await Task.Delay(TimeSpan.FromSeconds(1.5)); return 42; }); 

Новейшая версия C # – C # 7.1 позволяет создать консольное приложение async. Чтобы включить C # 7.1 в проекте, вам необходимо обновить VS до не менее 15.3 и изменить версию C# 7.1 на C# 7.1 или C# latest minor version . Для этого перейдите в Свойства проекта -> Сборка -> Дополнительно -> Языковая версия.

После этого следующий код будет работать:

 internal class Program { public static async Task Main(string[] args) { (...) } 

Чтобы избежать зависания, когда вы вызываете функцию где-то вниз по стеку вызовов, который пытается повторно присоединиться к текущему streamу (который застрял в Wait), вам необходимо сделать следующее:

 class Program { static void Main(string[] args) { Bootstrapper bs = new Bootstrapper(); List list = Task.Run((Func>>)bs.GetList).Result; } } 

(приведение требуется только для устранения двусмысленности)

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

 static void Main(string[] args) { Task.Run(async () => { await Task.WhenAll(jobslist.Select(nl => RunMulti(nl))); }).GetAwaiter().GetResult(); } private static async Task RunMulti(List joblist) { await ... } 
  • Как мне разрешить ввод номера в мое консольное приложение C #?
  • .NET Console TextWriter, который понимает Отступ / Неподвижный / IndentLevel
  • Как открыть окно консоли
  • Как остановить автоматическое закрытие приложений C #?
  • Запустить консольное приложение из другого консольного приложения
  • Установка позиции курсора в консольном приложении Win32
  • Как распечатать UTF-8 из консольного приложения c ++ в Windows
  • Какова команда выхода из приложения Console на C #?
  • Как написать быстрый цветной вывод на консоль?
  • Что происходит, когда вы закрываете консольное приложение c ++
  • Interesting Posts

    jq не работает с именем тега с тире и цифрами

    Строки кадра данных заказа согласно вектору с определенным порядком

    Почему я не могу создать массив большого размера?

    Разработка API-интерфейсов C для объектно-ориентированного кода на C ++

    В чем разница между eq, eql, equal и equalp в Common Lisp?

    Spring Контроллеры на основе аннотаций не работают, если они находятся внутри файла jar

    Как использовать jackson для десериализации массива объектов

    Котировочные знаки внутри строки

    Как сделать снимок экрана с помощью элемента управления WPF?

    Какие недостатки существуют при использовании памяти, которая быстрее, чем у материнской платы?

    Как Microsoft создала сборки, имеющие круговые ссылки?

    Может ли class C # наследовать атрибуты из своего интерфейса?

    Как заменить несколько строк в файле с помощью PowerShell

    ASP.NET MVC Отключить проверку на стороне клиента на уровне поля

    См. Доступные диски из Windows CLI?

    Давайте будем гением компьютера.