Не удается указать модификатор «async» в методе «Main» консольного приложения - PullRequest
393 голосов
/ 09 февраля 2012

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

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

public class Bootstrapper {

    public async Task<List<TvChannel>> GetList()
    {
        GetPrograms pro = new GetPrograms();

        return await pro.DownloadTvChannels();
    }
}

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

Ответы [ 15 ]

343 голосов
/ 07 июля 2014

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

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

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

Редактировать: включить решение Эндрю для необъяснимых исключений.

336 голосов
/ 09 февраля 2012

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

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

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

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

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

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

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

Если у вас есть машина с Async CTP, вы можете использовать GeneralThreadAffineContext из Мои документы \ Microsoft Visual Studio Async CTP \ Samples (C # Testing) Unit Testing \ AsyncTestUtilities.Кроме того, вы можете использовать AsyncContext из my 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();
    }
}

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

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<T>.Теперь вы можете сделать это:

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

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

90 голосов
/ 10 июля 2013

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

class Program
{
    static void Main(string[] args)
    {
        Bootstrapper bs = new Bootstrapper();
        var getListTask = bs.GetList(); // returns the Task<List<TvChannel>>

        Task.WaitAll(getListTask); // block while the task completes

        var list = getListTask.Result;
    }
}
74 голосов
/ 03 сентября 2014

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

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

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)
{
    ...
}
66 голосов
/ 30 мая 2017

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

public static Task Main();
public static Task<int> Main();
public static Task Main(string[] args);
public static Task<int> 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, необходимо щелкнуть правой кнопкой мыши проект и нажать «Свойства »затем перейдите на вкладку« Сборка ».Там нажмите расширенную кнопку внизу:

enter image description here

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

enter image description here

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

19 голосов
/ 03 сентября 2014

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

class Program
{
    static void Main(string[] args)
    {
        MainAsync(args).Wait();
    }

    static async Task MainAsync(string[] args)
    {
        // Code here
    }
}
18 голосов
/ 19 сентября 2017

C # 7.1 (с использованием vs 2017 года обновление 3) вводит асинхронную основную

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

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

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

Обновление:

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

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

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

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

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

Метод 1: Использование окна настроек проекта:

  • Откройте настройки вашего проекта
  • Выберите вкладку Build
  • Нажмите кнопку Advanced
  • Выберите нужную версию, как показано на следующем рисунке:

enter image description here

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

Добавить это свойство:

    <LangVersion>7.1</LangVersion>

пример:

    <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
        <PlatformTarget>AnyCPU</PlatformTarget>
        <DebugSymbols>true</DebugSymbols>
        <DebugType>full</DebugType>
        <Optimize>false</Optimize>
        <OutputPath>bin\Debug\</OutputPath>
        <DefineConstants>DEBUG;TRACE</DefineConstants>
        <ErrorReport>prompt</ErrorReport>
        <WarningLevel>4</WarningLevel>
        <Prefer32Bit>false</Prefer32Bit>
        <LangVersion>7.1</LangVersion>
    </PropertyGroup>    
17 голосов
/ 06 сентября 2016

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

  • У вас есть async Task MainAsync , как сказал Йохан .
  • Вызовите его .GetAwaiter().GetResult(), чтобы перехватить основное исключение , как do0gСказал .
  • Поддержка отмены , как сказал Кори .
  • Секунда CTRL+C должна немедленно прекратить процесс.(Спасибо binki !)
  • Обрабатывать 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<int> MainAsync(string[] args, CancellationToken cancellationToken)
{
    // Your code...

    return await Task.FromResult(0); // Success.
}
7 голосов
/ 12 марта 2014

Для асинхронного вызова задачи из 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

4 голосов
/ 22 августа 2017

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

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

internal class Program
{
    public static async Task Main(string[] args)
    {
         (...)
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...