Не могу указать модификатором async на метод main консольного приложения

Я новичок в асинхронном программировании с 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 ответов


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

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

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

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

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

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

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

если у вас есть машина с асинхронным CTP, вы можете использовать GeneralThreadAffineContext С Мои документы\Microsoft Visual Studio Async CTP\Samples (C# тестирование) модульное тестирование\AsyncTestUtilities. Кроме того, вы можете использовать AsyncContext С мой Нито.Пакет NuGet AsyncEx.

вот пример использования 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 пока нет спецификации языка, поэтому это только предположение.


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

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

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

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


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

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

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

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

В 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

EDIT:

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

enter image description here

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

enter image description here

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


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

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) вводит async main

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

   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
  • Нажмите кнопку Дополнительно
  • выберите нужную версию Как показано в следующем рисунок:

enter image description here

Method2: изменить PropertyGroup of .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>    

если вы используете C# 7.1 или более позднюю версию, перейдите к Науфаль в и просто измените тип возврата вашего основного метода на 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.
}

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

  1. задач.Run () для .NET 4.5

  2. задач.Фабрика.StartNew() для .NET 4.0 (может потребоваться Microsoft.Bcl.Асинхронная библиотека для ключевых слов async и await)

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


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

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

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

если вы не начали любой другой изображения threads, ваша программа выйдет, когда Main завершается, даже если он начал заниматься подготовительной работой.

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

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

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

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

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

на MSDN, документация для задач.Выполнить Метод (Действие) предоставляет этот пример, который показывает, как запустить метод асинхронно из 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

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

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

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

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

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

(см. Обработка Исключений (Параллельная Библиотека Задач) как включить обработку исключений для борьбы с AggregateException.)


наконец, на MSDN из документации для задач.Метод Задержки (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 to Task.Run, вы можете вместо этого передать лямбда функция такова:

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

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

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

(приведение требуется только для разрешения неоднозначности)


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

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