Невозможно указать модификатор "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 используйте
Подробнее:
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 ...
}