Program

First, let’s start with a new .NET Core 2.1 console app.

Next we need to add PostSharp to the project. If you have the extension for Visual Studio then you can right click on the project and add PostSharp to it through the menu. You can also use the Package Manager Console to run Install-Package PostSharp

You will also need to add the following NuGet packages:

PostSharp.Patterns.Common The bulk of the code contracts and aspects.

The bulk of the code contracts and aspects. PostSharp.Patterns.Diagnostics Tracing, logging, and diagnostics.

Tracing, logging, and diagnostics. PostSharp.Patterns.Diagnostics.Serilog Automatically inject Serilog.

Automatically inject Serilog. PostSharp.Patterns.Threading Threading models, dispatching, and deadlock detection.

Threading models, dispatching, and deadlock detection. Serilog Logging framework.

Logging framework. Serilog.Sinks.Console and Serilog.Enrichers.Thread for the sink.

Now that we have our packages, let’s drop down one layer away from Program for now. I suggest a sealed runtime layer with static properties and an isolated, non-static entry-point. This allows the runtime to be instantiated as an object with its own threading model which can then launch onto a platform compatible with the caller’s context. The static properties are isolated away from the entry point and can then be used to attach async monitors or metrics later on.

Now that we have a basic structure, we can talk about threading models. PostSharp provides three major features in PostSharp.Patterns.Threading.

Threading Models : A threading model is a design pattern that gives guarantees that your code executes safely on a multi-core computer.

: A threading model is a design pattern that gives guarantees that your code executes safely on a multi-core computer. Thread Dispatching : Custom attributes DispatchedAttribute and BackgroundAttribute cause the execution of a method to be dispatched to the UI thread or to a background thread, respectively.

: Custom attributes and cause the execution of a method to be dispatched to the UI thread or to a background thread, respectively. Deadlock Detection: Detects deadlocks at run time and throws an exception instead of allowing your application to freeze.

Threading models are named solutions to recurring problems and can be considered a design pattern which we will inject at compile time. Defects are discovered by validating the code both during build and while running. If the application is improperly threaded then it will fail in a deterministic way inside of these models. Without this assurance, race conditions or deadlocks tend to show up randomly and can corrupt data without any warning.

The primary threading model we will be looking at is the Actor model. Actors are services which run with their own state and a mailbox to receive messages in synchronous order. Their execution is more complex than this image describes, but they can be thought of as independent objects which run asynchronously against a queue of messages.

Actors do not map one-to-one with threads but it is helpful for illustrating execution.

The second threading model being used is the Freezable threading model. An immutable object can be safely accessed from multiple threads but the restrictions of immutability often makes configuration difficult. By using Freezable objects we can define at what point in time the object becomes immutable while still benefiting from mutability during creation.

Now let’s wrap the Runtime class with a frozen model.

To freeze an object we need to decorate it with an aspect called [Freezable] . To enforce most threading models, PostSharp relies on the [Aggregatable] attribute; this specifies that a class has its Parent/Child relationship explicitly defined. Any properties which are not children should be marked as a [Reference] . Any methods which return the same value for each input and make no observable state changes can be marked as [Pure] to inform PostSharp that they are safe in a threaded context. I opted to include [NotNull] to indicate which properties are available from the entry point.

Now that the Runtime class has been updated we can move on to the Platform . While developing for a cross-platform application on .NET Core, it’s helpful to have a layer where concerns of the platform can be safely handled. Let’s take a look at some functions you might see here:

GenericHost Creating the .NET generic host.

Creating the .NET generic host. GetNativeClass Services may need to load platform-specific implementations and this will provide the correct type.

Services may need to load platform-specific implementations and this will provide the correct type. Crash If we are going to inject threading models safely then services also need a way to crash if state becomes fatal.

You will see the following patterns on the Platform code:

[Required] Throws if null is passed in so that the responsibility belongs to the caller.

Throws if null is passed in so that the responsibility belongs to the caller. INativeClass Serves as an interface to load and terminate native classes after importing them.

Serves as an interface to load and terminate native classes after importing them. .Crash() Allows native platform classes to call for immediate debugging or termination if a fatal state is reached. It is marked with [ContractAnnotation (“=> halt”)] which specifies that for any given input the output will be a halted state. This allows the IDE to emit warnings appropriately.

Allows native platform classes to call for immediate debugging or termination if a fatal state is reached. It is marked with which specifies that for any given input the output will be a halted state. This allows the IDE to emit warnings appropriately. GenericHost Will be decorated with [Reference] instead of [Child] which doesn’t guarantee thread safety anymore at the boundary but also doesn’t force the host to implement a compatible model.

Now we have a Runtime with a thread safe entry point and a Platform which can load native classes. At this point we can go ahead and use the .NET Generic Host, specifically HostBuiler to configure services and host them.

Several packages are helpful for using the HostBuilder:

The .Start() method is decorated with a [SingleEntryMethod] attribute which automatically intercepts the method and returns null if it has been called more than once. This permits us to leave a reference to the Generic Host inside of the Platform without worrying about this method being called later on. Because this class is being intercepted, we can decorate it with the [ExplicitlySynchronized] aspect which informs PostSharp not to verify the safety of this class.

[SingleEntryMethod] is a custom aspect written with PostSharp. To create one of these we only need to decorate a class with [PSerializable] and then inherit from any of the PostSharp aspects. In this case we want to intercept calls at the boundary of the decorated method which is a feature provided by MethodInterceptionAspect .

SingleEntryMethodAttribute becomes [SingleEntryMethod]

As you can see in the Generic Host, one service was registered called TimingService . Let’s set that class up now. This will use the [Actor] threading model with a few new attributes:

[Actor] Generates code which prevents fields of an actor from being accessed from an invalid context. Requires [Aggregatable] parent/child decoration as well as the following two aspects.

Generates code which prevents fields of an actor from being accessed from an invalid context. Requires parent/child decoration as well as the following two aspects. [Reentrant] This attribute declares that an async method can be safely re-entered on each await statement. For the actor model, this means other methods can be invoked while waiting. This must be applied to all async actor methods.

This attribute declares that an async method can be safely re-entered on each await statement. For the actor model, this means other methods can be invoked while waiting. This must be applied to all async actor methods. [ExplicitlySynchronized] Opts out of the threading model by declaring that the object is handling its own safety.

Opts out of the threading model by declaring that the object is handling its own safety. [EntryPoint] This specifies that the method can be invoked from threads which do not currently have access to the object. This means an event handler, background task, or callback can safely enter the threading model. Only needs to be applied to private methods.

The TimingService class also inherits from IHostedService which integrates with the .NET Generic Host which is covered by this MSDN article. Each of these services will have StartAsync() called in the order they were registered during configuration earlier in the GenericHost class. The queue reverses this order when StopAsync() is called, starting with the last service registered and working its way back through the queue.

In the interest of keeping this example small we’re going to cheat here and just use a few Timer objects to run on threads and report back. This should illustrate that our class is appropriately using the threading model. These aren’t technically native namespaces either, but calling out to platform-specific code will work exactly the same way without any configuration.

With that in mind we need to implement the NativeTimers class which is imported from the Platform into TimingService after StartAsync() is called.

NativeTimers will need to be placed under a namespace compatible with the platform this code is running on. In my case I am running on Windows 10 17134 which will return “Win32NT” as the Platform. A list of potential platforms is provided by Microsoft.

MacOSX

Unix

Win32NT

At this point we have created the following areas of code:

Runtime Entered from Program, configures logging and then sets up the platform.

Entered from Program, configures logging and then sets up the platform. Platform Contains methods for loading native types and starts the generic host.

Contains methods for loading native types and starts the generic host. Generic Host .NET Core Generic Host which configures and creates services.

.NET Core Generic Host which configures and creates services. TimingService An example service being threaded with the Actor model.

An example service being threaded with the Actor model. NativeTimers An example being loaded natively by the Timing Service.

Now we just need to inject the logging wrapper and freeze our layers, starting with the .Entry() function in Runtime .

You will see the following patterns here:

LoggingServices.DefaultBackend This is the PostSharp logging back end. By setting our logger here we can then inject it later on.

This is the PostSharp logging back end. By setting our logger here we can then inject it later on. Post.Cast<T, IFreezable>( object ).Freeze() This is the call to freeze an object and mark it as immutable.

The VerboseLogger() implementation and BlueConsole theme:

Last we just need to call the Runtime from the program and inject logging into the assembly.

Create a new GlobalAspects.cs class without any using or namespace declarations. Include these lines:

This will wrap all Public , Private , Internal , and Protected members with the [Log] attribute from the assembly level.

Now just call the runtime from Program and watch it run.

If everything is set up correctly then we should be able to watch the entire flow of execution print out to the console.

We expect to see something like this: