Невозможно указать модификатор "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<TvChannel>> GetList()
    {
        GetPrograms pro = new GetPrograms();

        return await pro.DownloadTvChannels();
    }
}

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

Ответ 1

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

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

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

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

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

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

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

Если у вас есть машина с асинхронным CTP, вы можете использовать GeneralThreadAffineContext из My Documents\Microsoft Visual Studio Async CTP\Samples (С# тестирование) Модульное тестирование \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();
    }
}

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

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() для блокировки основного потока. Однако пока нет спецификации языка для С# 7.1, так что это только предположение.

Ответ 2

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

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

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

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

Ответ 3

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

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;
    }
}

Ответ 4

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

Одна из больших вещей в 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)
{
    ...
}

Ответ 5

В С# 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

EDIT:

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

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

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

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

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

Ответ 6

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

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

    static async Task MainAsync(string[] args)
    {
        // Code here
    }
}

Ответ 7

С# 7.1 (с использованием обновления vs 2017 3) вводит основной асинхронный

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

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

Подробнее Серия С# 7, часть 2: Главная Async

Update:

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

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

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

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

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

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

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

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

Метод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>    

Ответ 8

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

  • Имейте 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<int> MainAsync(string[] args, CancellationToken cancellationToken)
{
    // Your code...

    return await Task.FromResult(0); // Success.
}

Ответ 9

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

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

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

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

Ответ 10

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

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

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

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

Ответ 11

В основном попробуйте изменить вызов GetList на:

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

Ответ 12

В MSDN документация для Task.Run Method (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

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

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

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

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

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

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


Наконец, в MSDN из документации для Task.Delay Method (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 вы можете передать функцию лямбда следующим образом:

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

Ответ 13

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

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

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

Ответ 14

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

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

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

Ответ 15

В моем случае у меня был список заданий, которые я хотел запустить в 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<string> joblist)
{
    await ...
}