可以在CoreCLR上使用’async’修饰符标记入口点?

在Stephan Cleary最近关于.NET CoreCLR上的异步控制台应用程序的博客文章中,他向我们展示了在CoreCLR中(当前在Visual Studio 2015上运行,CTP6),入口点“Main”实际上可以标记为async Task ,正确编译并实际运行:

 public class Program { public async Task Main(string[] args) { Console.WriteLine("Hello World"); await Task.Delay(TimeSpan.FromSeconds(1)); Console.WriteLine("Still here!"); Console.ReadLine(); } } 

给出以下输出:

async主要入口点

这是由ASP.NET团队的一篇名为“深度潜水”的博客文章强化到ASP.NET 5运行时 :

除了静态的Program.Main入口点,KRE还支持基于实例的入口点。 您甚至可以使主入口点异步并返回任务。 通过使主入口点成为实例方法,您可以将运行时环境中的服务注入到应用程序中。

我们知道,到目前为止, 无法使用’async’修饰符标记入口点 。 那么,在新的CoreCLR运行时中,这实际上是如何实现的呢?

深入了解CoreCLR运行时的源代码,我们可以看到一个名为RuntimeBootstrapper的静态类,它负责调用我们的入口点:

 public static int Execute(string[] args) { // If we're a console host then print exceptions to stderr var printExceptionsToStdError = Environment.GetEnvironmentVariable(EnvironmentNames.ConsoleHost) == "1"; try { return ExecuteAsync(args).GetAwaiter().GetResult(); } catch (Exception ex) { if (printExceptionsToStdError) { PrintErrors(ex); return 1; } throw; } } 

我们可以在内部看到它调用ExecuteAsync(args).GetAwaiter().GetResult(); ,在语义上等同于调用Task.Result ,除了不是接收包装的AggregationException ,我们收到解包的exception。

这一点很重要,因为没有关于它是如何发生的“黑魔法” 。 对于当前版本的CoreCLR运行时,允许该方法标记为async Task因为它被运行时阻塞在调用链的上方。

附注:

潜入ExecuteAsync ,我们会看到它最终调用:

 return bootstrapper.RunAsync(app.RemainingArguments); 

在查看内部时 ,我们看到了入口点的实际MethodInfo调用:

 public static Task Execute(Assembly assembly, string[] args, IServiceProvider serviceProvider) { object instance; MethodInfo entryPoint; if (!TryGetEntryPoint(assembly, serviceProvider, out instance, out entryPoint)) { return Task.FromResult(-1); } object result = null; var parameters = entryPoint.GetParameters(); if (parameters.Length == 0) { result = entryPoint.Invoke(instance, null); } else if (parameters.Length == 1) { result = entryPoint.Invoke(instance, new object[] { args }); } if (result is int) { return Task.FromResult((int)result); } if (result is Task) { return (Task)result; } if (result is Task) { return ((Task)result).ContinueWith(t => { return 0; }); } return Task.FromResult(0); }