无法在控制台应用的“主要”方法中指定“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
修饰符,因此如何在main
asynchronous运行代码?
正如你发现的那样,在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调用任务,请使用
-
Task.Run()for .NET 4.5
-
用于.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)的文档提供了这个例子,它显示了如何从main
asynchronous运行一个方法:
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对他的回答的评论 :
你可以使用一个简单的
Wait
或Result
,没有什么错。 但请注意,有两个重要的区别: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.1
或C# 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 ... }