Не удается указать модификатор' 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 асинхронно?

648   15  

15 ответов:

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

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

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

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

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

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

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

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

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

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 вы сможете сделать правильный async 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 in консольные приложения.

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

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 был введен, вы, конечно,может знаком с async... хотя, как правило,это была не очень хорошая идея. Я считаю, что это было изменено выпуском VS 2013, чтобы стать ошибкой.

если вы не начали какой-либо другой изображения темы, ваша программа выйдет, когда 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.

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

вы можете использовать простой Wait или Result и нет ничего плохого с этим. Но имейте в виду, что есть два важных отличия: 1) все async продолжения выполняются в пуле потоков, а не в основном нить, и 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 до 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 ...
}

Comments

    Ничего не найдено.