在Visual Studio 2012中,编译器会禁止使用
async Main
方法。这在Visual Studio 2010中使用Async CTP时是允许的(但从未推荐过)。
从Visual Studio 2017更新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的语言规范,所以这只是一种假设。
我有关于
async/await和
异步控制台程序的博客文章。以下是介绍文章中的一些背景信息:
如果"await"发现可等待对象尚未完成,则它会以异步方式执行。它告诉可等待对象在完成时运行方法的剩余部分,然后从异步方法中返回。当传递方法的剩余部分给可等待对象时,await还会捕获当前的上下文。
稍后,当可等待对象完成时,它将在捕获的上下文中执行异步方法的剩余部分。
这就是为什么在具有async Main
的控制台程序中会出现问题的原因:
记住我们在介绍帖子中提到的,异步方法会在完成之前返回给调用者。这在UI应用程序中非常完美(方法只是返回到UI事件循环),以及ASP.NET应用程序中(方法返回到线程之外,但保持请求活动)。但对于控制台程序来说,情况就不太理想了:Main方法返回给操作系统,所以你的程序就退出了。
一个解决方案是为控制台程序提供自己的上下文 - 一个与异步兼容的“主循环”。
如果你的机器上安装了Async CTP,你可以使用"My Documents\Microsoft Visual Studio Async CTP\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();
}
}
或者,你可以简单地阻塞主控制台线程,直到你的异步工作完成为止。
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
包装。