В недавнем сообщении в блоге Стефана Клири о асинхронных консольных приложениях. на .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 среда выполнения ASP.NET 5:
В дополнение к статической точке входа Program.Main KRE поддерживает точки входа на основе экземпляров. Вы даже можете сделать основную точку входа асинхронной и вернуть Task. Имея основной точкой входа метод экземпляра, вы можете внедрить сервисы в ваше приложение средой выполнения.
Мы знаем, что до сих пор точка входа не может быть помечены модификатором 'async'. Итак, как это возможно в новой среде выполнения CoreCLR?
Ответ 1 · рейтинг 4 · Yuval Itzchakov
Погружаясь в исходный код среды выполнения 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);
}