Я новичок в асинхронном программировании с 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
асинхронном режиме?
c#
.net
asynchronous
console-application
Даниелович
источник
источник
Ответы:
Как вы обнаружили, в VS11 компилятор запрещает
async Main
метод. Это было разрешено (но никогда не рекомендуется) в VS2010 с Async CTP.У меня есть последние сообщения в блоге об асинхронных / ожидающих и , в частности, асинхронных консольных программах . Вот некоторая справочная информация из вступительного поста:
Вот почему это проблема в консольных программах с
async Main
:Одним из решений является предоставление собственного контекста - «основного цикла» для вашей консольной программы, который является асинхронным.
Если у вас есть машина с Async CTP, вы можете использовать
GeneralThreadAffineContext
из Моих документов \ Microsoft Visual Studio Async CTP \ Samples (C # Testing) Unit Testing \ AsyncTestUtilities . Кроме того, вы можете использоватьAsyncContext
из моего пакета Nito.AsyncEx NuGet .Вот пример использования
AsyncContext
;GeneralThreadAffineContext
имеет почти идентичное использование:Кроме того, вы можете просто заблокировать основной поток консоли, пока ваша асинхронная работа не будет завершена:
Обратите внимание на использование
GetAwaiter().GetResult()
; это позволяет избежатьAggregateException
упаковки, которая происходит, если вы используетеWait()
илиResult
.Обновление, 2017-11-30: Начиная с Visual Studio 2017, обновление 3 (15.3), язык теперь поддерживает
async Main
- до тех пор, пока он не вернетTask
илиTask<T>
. Теперь вы можете сделать это:Семантика выглядит так же, как
GetAwaiter().GetResult()
стиль блокировки основного потока. Тем не менее, пока нет спецификации языка для C # 7.1, так что это только предположение.источник
Wait
илиResult
, и в этом нет ничего плохого. Но имейтеasync
в виду, что есть два важных различия: 1) все продолжения выполняются в пуле потоков, а не в основном потоке, и 2) все исключения помещаются вAggregateException
.<LangVersion>latest</LangVersion>
в файл csproj, как показано здесь .Вы можете решить это с помощью этой простой конструкции:
Это поместит все, что вы делаете, в ThreadPool, где вы этого хотите (поэтому другие задачи, которые вы запускаете / ожидаете, не пытаются присоединиться к потоку, которые они не должны), и ждут, пока все будет сделано, прежде чем закрывать консольное приложение. Нет необходимости в специальных петлях или наружных конечностях.
Изменить: включить решение Эндрю для необъяснимых исключений.
источник
Wait()
на,GetAwaiter().GetResult()
вы избежитеAggregateException
обертку, когда вещи бросают.async main
вводится в C # 7.1, на момент написания этой статьи.Вы можете сделать это без необходимости использования внешних библиотек, выполнив следующие действия:
источник
getListTask.Result
это также блокирующий вызов, и поэтому приведенный выше код может быть написан безTask.WaitAll(getListTask)
.GetList
throws вам придется пойматьAggregateException
и опросить его исключения, чтобы определить фактическое выброшенное исключение. Вы можете, однако, вызов ,GetAwaiter()
чтобы получитьTaskAwaiter
дляTask
, и вызовGetResult()
на что, то естьvar list = getListTask.GetAwaiter().GetResult();
. При получении результата отTaskAwaiter
(также блокирующего вызова) любые сгенерированные исключения не будут помещены вAggregateException
.В C # 7.1 вы сможете сделать правильную асинхронную Main . Соответствующие подписи для
Main
метода были расширены до:Например, вы могли бы делать:
Во время компиляции асинхронный метод точки входа будет переведен для вызова
GetAwaitor().GetResult()
.Подробности: https://blogs.msdn.microsoft.com/mazhou/2017/05/30/c-7-series-part-2-async-main
РЕДАКТИРОВАТЬ:
Чтобы включить функции языка C # 7.1, вам нужно щелкнуть правой кнопкой мыши по проекту и нажать «Свойства», а затем перейти на вкладку «Сборка». Там нажмите расширенную кнопку внизу:
В раскрывающемся меню языковой версии выберите «7.1» (или любое более высокое значение):
По умолчанию используется «последняя основная версия», которая оценивает (на момент написания этой статьи) C # 7.0, который не поддерживает асинхронное main в консольных приложениях.
источник
Я добавлю важную функцию, которую пропустили все остальные ответы: отмена.
Одна из важных вещей в TPL - поддержка отмены, а в консольных приложениях есть встроенный метод отмены (CTRL + C). Это очень просто связать их вместе. Вот как я структурирую все свои асинхронные консольные приложения:
источник
Wait()
также?Wait()
, он не будет ждать завершения асинхронного кода - он перестанет ждать и немедленно завершит процесс.Wait()
метод передается с тем же маркером. То, что я пытаюсь сказать, это то, что это не имеет никакого значения.C # 7.1 (с использованием vs 2017 года обновление 3) вводит основной асинхронный
Ты можешь написать:
Для получения дополнительной информации C # 7 Series, часть 2: Async Main
Обновить:
Вы можете получить ошибку компиляции:
Эта ошибка связана с тем, что vs2017.3 по умолчанию настроен как c # 7.0, а не c # 7.1.
Вы должны явно изменить настройки вашего проекта, чтобы установить функции c # 7.1.
Вы можете установить c # 7.1 двумя способами:
Способ 1. Использование окна настроек проекта:
Метод 2: Изменить PropertyGroup .csproj вручную
Добавьте это свойство:
пример:
источник
Если вы используете C # 7.1 или более позднюю версию, следуйте ответу nawfal и просто измените тип возврата вашего метода Main на
Task
илиTask<int>
. Если вы не:async Task MainAsync
как сказал Йохан ..GetAwaiter().GetResult()
чтобы перехватить основное исключение, как сказал do0g .CTRL+C
должна немедленно прекратить процесс. (Спасибо, Бинки !)OperationCancelledException
- вернуть соответствующий код ошибки.Окончательный код выглядит так:
источник
e.Cancel = true
это безоговорочно.Пока мне это не нужно, но когда я использовал консольное приложение для быстрых тестов и требовал асинхронности, я просто решил это следующим образом:
источник
SynchronizationContext
связи с основным потоком. Таким образом, он не блокируется, потому что даже безConfigureAwait(false)
этого все продолжения будут выполняться в пуле потоков.Для асинхронного вызова задачи из 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
источник
В Main попробуйте изменить вызов GetList на:
источник
Когда был представлен CTP CTP, вы наверняка могли пометить Main как
async
... хотя обычно это было не очень хорошая идея. Я полагаю, что это было изменено выпуском VS 2013, чтобы стать ошибкой.Если вы не запустили какие-либо другие потоки переднего плана , ваша программа завершит работу, когда
Main
завершит работу, даже если она запустила некоторую фоновую работу.Что ты на самом деле пытаешься сделать? Обратите внимание, что ваш
GetList()
метод на самом деле не должен быть асинхронным в данный момент - он добавляет дополнительный слой без реальной причины. Это логически эквивалентно (но сложнее чем):источник
DownloadTvChannels()
возвращает? Предположительно это возвращаетTask<List<TvChannel>>
не так ли? Если нет, то вряд ли вы сможете его дождаться. (Возможно, учитывая модель awaiter, но маловероятно.) Что касаетсяMain
методы - он по- прежнему должен быть статическим ... вы заменить наstatic
модификатор сasync
модификатором возможно?public static async void Main() {}
? Но если онDownloadTvChannels()
уже возвращает aTask<List<TvChannel>>
, предположительно, он уже асинхронный - поэтому вам не нужно добавлять другой слой. Это стоит понять внимательно.Новейшая версия C # - C # 7.1 позволяет создавать асинхронные консольные приложения. Чтобы включить C # 7.1 в проекте, вам нужно обновить VS как минимум до 15.3 и изменить версию C # на
C# 7.1
orC# latest minor version
. Для этого перейдите в Свойства проекта -> Сборка -> Дополнительно -> Языковая версия.После этого будет работать следующий код:
источник
В MSDN документация для метода Task.Run (Action) предоставляет этот пример, который показывает, как асинхронно запускать метод из
main
:Обратите внимание на это утверждение, которое следует за примером:
Итак, если вместо этого вы хотите, чтобы задача выполнялась в главном потоке приложений, посмотрите ответ по @StephenCleary .
Что касается потока, в котором выполняется задача, также обратите внимание на комментарий Стивена к его ответу:
(См. Обработка исключений (Task Parallel Library) для получения информации о том, как включить обработку исключений для работы с
AggregateException
.)Наконец, на MSDN из документации для метода Task.Delay (TimeSpan) этот пример показывает, как запустить асинхронную задачу, которая возвращает значение:
Обратите внимание, что вместо того, чтобы передавать
delegate
вTask.Run
, вы можете вместо этого передавать лямбда-функцию следующим образом:источник
Чтобы избежать зависания при вызове функции где-нибудь в стеке вызовов, которая пытается повторно присоединиться к текущему потоку (который застрял в ожидании), вам необходимо сделать следующее:
(приведение требуется только для устранения неоднозначности)
источник
В моем случае у меня был список заданий, которые я хотел запускать в асинхронном режиме из своего основного метода, уже давно использовал его в работе и отлично работает.
источник