Async Console Apps on .NET CoreCLR
• CommentsI finally had a chance this week to play around a bit with Visual Studio 2015 CTP 6. I hardly ever have the time to use CTP bits, but with all the talk about CoreCLR I just had to try it out. :)
This entire post is about prerelease technologies. Everything is subject to change.
In one of the recent articles about CoreCLR, Daniel Roth mentions in passing “You can even make the main entry point asynchronous and return a Task.”
Wait, what?
Yup, that’s right! Asynchronous Main
methods for Console applications! Let’s see how this works…
Getting Started with the CoreCLR CTP
If you want to follow along at home, first install the VS2015 CTP.
Next, you’ll need to install some K runtime stuff. The best instructions I’ve found for this are on the home page of the ASP.NET 5 GitHub repo. If you just want to get started quickly, open up PowerShell and execute the following commands:
Aside: How Many Runtimes?
It’s important to note that there are two K runtimes installed (well, four, if you count 32/64-bit variants). There’s the “ordinary” desktop CLR and the new CoreCLR. You can type kvm list
to show the installed runtimes and switch between them with kvm upgrade -r coreclr
and kvm upgrade -r clr
:
PS> kvm list
Active Version Runtime Architecture Location Alias
------ ------- ------- ------------ -------- -----
1.0.0-beta3 clr x64 C:\Users\stephen\.k\runtimes
* 1.0.0-beta3 clr x86 C:\Users\stephen\.k\runtimes default
1.0.0-beta3 coreclr x64 C:\Users\stephen\.k\runtimes
1.0.0-beta3 coreclr x86 C:\Users\stephen\.k\runtimes
PS> kvm upgrade -r coreclr
Determining latest version
kre-coreclr-win-x86.1.0.0-beta3 already installed.
Adding C:\Users\stephen\.k\runtimes\kre-coreclr-win-x86.1.0.0-beta3\bin to process PATH
Adding C:\Users\stephen\.k\runtimes\kre-coreclr-win-x86.1.0.0-beta3\bin to user PATH
Updating alias 'default' to 'kre-coreclr-win-x86.1.0.0-beta3'
PS> kvm list
Active Version Runtime Architecture Location Alias
------ ------- ------- ------------ -------- -----
1.0.0-beta3 clr x64 C:\Users\stephen\.k\runtimes
1.0.0-beta3 clr x86 C:\Users\stephen\.k\runtimes
1.0.0-beta3 coreclr x64 C:\Users\stephen\.k\runtimes
* 1.0.0-beta3 coreclr x86 C:\Users\stephen\.k\runtimes default
PS> kvm upgrade -r clr
Determining latest version
kre-clr-win-x86.1.0.0-beta3 already installed.
Adding C:\Users\stephen\.k\runtimes\kre-clr-win-x86.1.0.0-beta3\bin to process PATH
Adding C:\Users\stephen\.k\runtimes\kre-clr-win-x86.1.0.0-beta3\bin to user PATH
Updating alias 'default' to 'kre-clr-win-x86.1.0.0-beta3'
PS> kvm list
Active Version Runtime Architecture Location Alias
------ ------- ------- ------------ -------- -----
1.0.0-beta3 clr x64 C:\Users\stephen\.k\runtimes
* 1.0.0-beta3 clr x86 C:\Users\stephen\.k\runtimes default
1.0.0-beta3 coreclr x64 C:\Users\stephen\.k\runtimes
1.0.0-beta3 coreclr x86 C:\Users\stephen\.k\runtimes
The regular desktop CLR is sitting on top of the full .NET framework. The CoreCLR is a new runtime. This is the one that a lot of people refer to as “cloud optimized” - though in reality, it’s just more of a microkernel-based framework as opposed to the traditional monolithic one. I expect we’ll see some great things from CoreCLR other than just running ASP.NET apps in the cloud (though it will of course be good for that too!) - the microkernel “only pay for what you use” architecture is perfectly suited for mobile platforms - and even smaller-than-mobile platforms!
Creating a CoreCLR Console Application
Let’s get started! In the CTP6, to create a CoreCLR console app, you choose New Project, and then…
Select “Web” on the left-hand side, and then “ASP.NET 5 Console Application”.
Wait, what?
Yeah, “ASP.NET Console Application” is a ridiculous juxtaposition… This will probably be renamed completely as the tooling continues to develop. For now, though, the CoreCLR has strong roots in the ASP.NET world, so we create a CoreCLR Console application as an “ASP.NET Console” application.
Making It Asynchronous!
The default code the template gives you is pretty straightforward:
So let’s just rewrite it to be asynchronous:
Compile and run it, and you’ll see the Console application sticking around after the await
!
Note the title bar. This console application is actually just a dll, which is hosted in the K runtime for the full desktop CLR. So this is actually running on whatever .NET desktop framework you have installed.
Running on the CoreCLR
To run on the actual CoreCLR, select the dropdown arrow of the Start button and choose the .NET Core as your CLR:
Then when you run it, you’ll be running on the CoreCLR (again, note the title bar):
Compiling for Multiple Frameworks
It’s important to peek under the covers a bit. Pop open your project.json
. See the frameworks
section?
There are two frameworks for this project: aspnet50
is the regular desktop CLR-based framework - it’s the one that runs on the full .NET desktop framework. The aspnetcore50
is the CoreCLR framework - it’s the new microkernel one. Since CoreCLR is a microkernel architecture, you need to explicitly bring in nearly everything you intend to use - in this case, the VS template automatically added a dependency on System.Console
for you. (You can add more dependencies by installing NuGet packages; System.Console
is in fact just a NuGet package that is already installed into this project).
The important takeaway here is that this project is compiled twice, once for each framework. And there are APIs in each framework that the other doesn’t have. For example, I have a weird quirk where I strongly prefer using ReadKey
instead of ReadLine
in my throwaway Console applications. But CoreCLR’s System.Console
package doesn’t support ReadKey
!
So, what happens if I change ReadLine
to ReadKey
? At first, it looks OK, but it will no longer build.
The confusing part here is that there’s no “red squigglies” or anything indicating that Console
doesn’t have a ReadKey
method. Just an error that seems to come out of nowhere.
The key is to use the navigation bar - VS is aware that this file is compiled two different ways because our project is supporting two frameworks. By switching the view of the file to the CoreCLR, I can now see the error:
Note that the “Project” column of the Error List window specifies the framework as well as the project. This should be the first thing to check if you’re seeing “impossible” or mysterious errors like this.
Conditional Compilation
Well, let’s say I really want to use ReadKey
?
One solution is to make use of conditional compilation. Remember, this project is being compiled separately for each platform. As Rick Strahl pointed out, each platform defines a compiler directive that is just the platform name in all uppercase:
That works! So, you can use conditional compilation to take advantage of different APIs for different frameworks.
Limiting Frameworks
The conditional compilation does work, but it also causes a change in behavior between the frameworks. This is not ideal.
Instead, I’m going to decide that ReadKey
is just plain critical for my demos, and so CoreCLR is out. Thanks for playing.
To limit the frameworks for my project, I just have to edit the project.json
file and remove the CoreCLR:
Save the file, and now my project only targets the full desktop CLR.
Removing the aspnetcore50
framework will probably have to be done a lot over the next few months. There are a lot of third-party libraries that do not support the CoreCLR (yet).
Incidentally, I provided this technique as a Stack Overflow answer earlier today.
Future Speculations
Currently, the “console application” we’re building is actually just a dll that’s hosted by a K runtime. I expect that when VS2015 gets to RTM, they’ll have a real executable popping out of the build. They have already started on something like that.
The old Console application type will undoubtedly stick around, for backwards compatibility at least. So, I’d expect VS2015 will have two Console application types: a legacy one that runs directly on the .NET Desktop Framework, and a new one that run on the K runtime (which in turn can run on either .NET Core CLR or .NET Desktop CLR). It’s the K runtime that gives us the power of an asynchronous entry point, so I’d expect only the new console application type would be able to have an async Main
.
I just have to say that it’s so cool to get async Main
support! :)