6

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

给出以下输出:

异步主入口点

ASP.NET 团队的一篇名为A Deep Dive into the ASP.NET 5 Runtime的博客文章强化了这一点:

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

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

4

1 回答 1

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

在查看内部时,我们看到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);
}
于 2015-03-09T09:26:58.037 回答