在Stephan Cleary最近关于Async Console Apps on .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();
}
}
提供以下输出:
来自ASP.NET团队的一篇名为A Deep Dive into the ASP.NET 5 Runtime的博客文章强化了这一点:
除了静态
Program.Main
入口点之外,KRE还支持 基于实例的入口点。您甚至可以创建主入口点 异步并返回一个Task。通过主入口点为 实例方法,您可以将服务注入您的应用程序 由运行时环境。
到目前为止,我们知道An entry point cannot be marked with the 'async' modifier。那么,在新的CoreCLR运行时中,这实际上是如何实现的呢?
答案 0 :(得分:4)
深入了解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
,我们收到解开的异常。
这一点很重要,因为没有"黑魔法" 关于它是如何发生的。对于当前版本的CoreCLR运行时,允许该方法标记为async Task
,因为它被运行时阻塞在调用链的上方。
潜入ExecuteAsync
,我们会看到它最终致电:
return bootstrapper.RunAsync(app.RemainingArguments);
When looking inside,我们看到对我们的入口点的实际MethodInfo
调用:
public static Task<int> 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<int>)
{
return (Task<int>)result;
}
if (result is Task)
{
return ((Task)result).ContinueWith(t =>
{
return 0;
});
}
return Task.FromResult(0);
}