无法在控制台应用的“主要”方法中指定“asynchronous”修饰符

我是新来的asynchronous编程与async修改器。 我想弄清楚如何确保我的控制台应用程序的Main方法实际上是asynchronous运行的。

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

我知道这不是从“顶部”asynchronous运行。 由于无法在Main方法中指定async修饰符,因此如何在mainasynchronous运行代码?

正如你发现的那样,在VS11中,编译器将禁止一个async Main方法。 这是允许(但从来没有推荐)在VS2010与asynchronousCTP。

我最近有关于asynchronous/等待和asynchronous控制台程序的博客文章。 以下是介绍post的一些背景信息:

如果“await”看到awaitable没有完成,那么它是asynchronous的。 它告诉等待完成时运行方法的其余部分,然后从asynchronous方法返回 。 当它将剩余的方法传递给awaitable时,等待也会捕获当前的上下文

稍后,等待完成时,它将执行asynchronous方法的其余部分(在捕获的上下文中)。

这是为什么这是一个async Main控制台程序中的问题:

记得从我们的介绍文章,一个asynchronous方法将返回到它的调用方在完成之前。 这在UI应用程序(该方法只是返回到UI事件循环)和ASP.NET应用程序(该方法返回线程,但保持请求活着)完美的作品。 对于控制台程序来说效果不好:主要返回到操作系统 – 所以你的程序退出。

一种解决scheme是提供自己的上下文 – 一个用于控制台程序的“主循环”,它是asynchronous兼容的。

如果您的计算机具有Async CTP,则可以使用My Documents \ Microsoft Visual StudioasynchronousCTP \ Samples(C#testing)unit testing\ AsyncTestUtilities中的GeneralThreadAffineContext 。 或者,您可以使用我的Nito.AsyncEx NuGet包中的 AsyncContext

这是一个使用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(); } } 

或者,您可以阻止主控制台线程,直到完成asynchronous工作:

 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() ; 这样可以避免在使用Wait()Result时发生的AggregateException包装。

你可以用这个简单的结构解决这个问题:

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

这将把你在ThreadPool上执行的所有事情放在你想要的地方(所以你启动/等待的其他任务不会尝试重新join他们不应该的Thread),并且等到所有事情完成之后再closuresConsole应用程序。 不需要特殊的循环或外部库。

编辑:结合安德鲁的解决未捕获的例外。

您也可以通过执行以下操作而无需外部库:

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

我会添加一个重要的function,所有其他的答案已经忽略了:取消。

TPL中的一件大事就是取消支持,控制台应用程序有一个内置取消的方法(CTRL + C)。 把它们绑在一起非常简单。 这是我如何构build我的所有asynchronous控制台应用程序:

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

还没有这个需要,但是当我使用控制台应用程序的快速testing和所需的asynchronous我刚刚解决了这样的事情:

 class Program { static void Main(string[] args) { MainAsync(args).Wait(); } static async Task MainAsync(string[] args) { // Code here } } 

在C#7.1中,你将能够做一个适当的asynchronous主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(); } 

在编译时,asynchronous入口点方法将被转换为调用GetAwaitor().GetResult()

详情: https : //blogs.msdn.microsoft.com/mazhou/2017/05/30/c-7-series-part-2-async-main

编辑:

要启用C#7.1语言function,您需要右键单击该项目,然后单击“属性”,然后转到“生成”选项卡。 在那里,点击底部的高级button:

在这里输入图像描述

从语言版本下拉菜单中select“7.1”(或更高的值):

在这里输入图像描述

默认是“最新的主要版本”,它将评估(在写这篇文章的时候)C#7.0,它不支持在控制台应用程序中的asynchronous主。

  • 有一个像Johan说的async Task MainAsync
  • 调用它的.GetAwaiter().GetResult()来捕获像do0g所说的基本exception。
  • 支持像Cory这样的取消说 。
  • Handle OperationCancelledException – 返回一个适当的错误代码。

最终的代码如下所示:

 private static int Main(string[] args) { var source = new CancellationTokenSource(); Console.CancelKeyPress += (s, e) => { e.Cancel = true; source.Cancel(); }; try { return MainAsync(args, source.Token).GetAwaiter().GetResult(); } catch (OperationCanceledException) { return 1223; // Cancelled. } } private static async Task<int> MainAsync(string[] args, CancellationToken token) { // Your code... return await Task.FromResult(0); // Success. } 

对于从Main中asynchronous调用任务,请使用

  1. Task.Run()for .NET 4.5

  2. 用于.NET 4.0的Task.Factory.StartNew()(可能需要Microsoft.Bcl.Async库进行asynchronous并等待关键字)

详细信息: http : //blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx

C#7.1(使用vs 2017更新3)引入了asynchronous主

你可以写:

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

欲了解更多细节C#7系列,第2部分:asynchronous主

更新:

您可能会收到一个编译错误:

程序不包含适用于入口点的静态“Main”方法

这个错误是由于vs2017.3默认configuration为c#7.0而不是c#7.1。

您应该明确修改您的项目设置以设置c#7.1function。

您可以通过两种方法设置c#7.1:

方法1:使用项目设置窗口:

  • 打开你的项目的设置
  • select生成选项卡
  • 点击高级button
  • select你想要的版本如下图所示:

在这里输入图像描述

方法2:手动修改.csproj的PropertyGroup

添加此属性:

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

在主要尝试改变调用GetList到:

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

在MSDN上, Task.Run方法(Action)的文档提供了这个例子,它显示了如何从mainasynchronous运行一个方法:

 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 

请注意下面的例子:

这些示例显示asynchronous任务在与主应用程序线程不同的线程上执行。

所以,如果您希望任务在主应用程序线程上运行,请参阅@StephenCleary 的答案 。

关于任务运行的线程,还要注意Stephen对他的回答的评论 :

可以使用一个简单的WaitResult ,没有什么错。 但请注意,有两个重要的区别:1)所有async连续运行在线程池而不是主线程上; 2)任何exception都包装在一个AggregateException

(请参阅exception处理(任务并行库)以了解如何合并exception处理来处理AggregateException 。)


最后,在MSDN上从Task.Delay方法(TimeSpan)的文档中,这个例子展示了如何运行一个返回值的asynchronous任务:

 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 ,而是可以像这样传递一个lambda函数:

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

当C#5 CTP被引入时,你当然可以用Main 标记async …虽然这通常不是一个好主意。 我相信VS 2013的发布改变了这个错误。

除非你已经启动了其他的前台线程,否则当Main完成时你的程序将会退出,即使它开始了一些后台工作。

真的想做什么? 请注意,您的GetList()方法现在确实不需要是asynchronous的 – 它没有真正的原因添加额外的层。 这在逻辑上相当于(但比以下更复杂):

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

C#的最新版本 – C#7.1允许创buildasynchronous控制台应用程序。 要在项目中启用C#7.1,必须将VS升级到至less15.3,并将C#版本更改为C# 7.1C# latest minor version 。 要做到这一点,转到项目属性 – >生成 – >高级 – >语言版本。

在此之后,以下代码将工作:

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

为了避免在试图重新join当前线程的调用堆栈下方的某个函数中调用某个函数(该函数停留在Wait中),您需要执行以下操作:

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

(演员只需要解决歧义)

在我的情况下,我有一个我想从我的主要方法asynchronous运行的工作列表,已经在生产中使用这个相当长的一段时间,工作正常。

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