posted on Apr 07, 2019

The problem

In a desktop application I'm building for work, we have a custom SynchronizationContext which dispatches all continuations to the UI thread. This makes things easier to reason with during development, since we are always on the same thread.

We developed a large chunk of our application with this approach. It was fine for a while, but as the number of continuations in our service layer increased, we began to notice some drawbacks using with this approach.

Inside of our service layer, we were using async/await for things that have no concern/knowledge of a GUI. HOwever, every continuation was still happening on the UI thread. As our service layer grew, animations that were once super smooth started to quickly become very choppy. Non-UI related code was being run on the UI for no good reason.

The solution

We needed a way to trigger each method in our service layer to popup off the current gui- SynchronizationContext and onto the thread pool. We immediately came up with a few solutions.

Litter our service layer with .ConfigureAwait(false) . Update our UI layer to clear/restore the SynchronizationContext whenever we call our service methods.

I didn't like either of these approaches because they required changing many lines of code and were (human) error prone. So I sought out another approach that will prevent us from changing any code, while also ensuring that every service method is not continuated on the UI thread. Consider the following:

public interface IService { Task RunMethod ( ) ; } public class Service : IService { public async Task RunMethod ( ) { await Task.Yield(); Thread.Sleep(TimeSpan.FromSeconds( 5 )); } } public class MyViewModel { private readonly IService _service; public MyViewModel ( IService service ) { _service = service; } public async Task OnButtonClicked ( ) { await _service.RunMethod(); } }

If this code were to be called, as is, when OnButtonClicked > RunMethod is called, Thread.Sleep would eventually be ran on the UI thread. Not good!

I need a way to wrap an instance of IService in another implementation of IService that simply clears the SynchronizationContext before called the inner IService . Consider the following:

public class NoSyncServiceWrapper : IService { private IService _inner; public NoSyncServiceWrapper ( IService inner ) { _inner = inner; } public Task RunMethod ( ) { var oldContext = SynchronizationContext.Current; SynchronizationContext.SetSynchronizationContext( null ); try { return _inner.RunMethod() } finally { SynchronizationContext.SetSynchronizationContext(oldContext); } } }

Now, if I pass NoSyncServiceWrapper to the MyViewModel , the concern of SynchronizationContext will be in neither my service layer or my UI layer. No code needs to be changed!

var service = new Service(); var viewModel = new MyViewModel( new NoSyncServiceWrapper(service)); await viewModel.OnButtonClicked();

The only problem now is that I have to manage a separate implementation of every service/method pair with this boiler plate code. Yeah.. I'd rather not..

Using Castle to dynamically generate the wrappers.

Castle.Core supports wrapping an instance of an interface with a dynamic implementation of the interface to perform some pre-post logic on the given instance. This is ideal for things like logging/profiling, and in our case, clearing/restoring the SynchronizationContext . It is actually pretty simple!

public static class ProxyWrapper { private static readonly ProxyGenerator _proxyGenerator = new ProxyGenerator(); private static readonly SyncContextInterceptor _syncContextInterceptor = new SyncContextInterceptor(); public static object WrapService ( Type serviceType, object instance ) { return _proxyGenerator.CreateInterfaceProxyWithTargetInterface(serviceType, instance, new ProxyGenerationOptions(), _syncContextInterceptor); } private class SyncContextInterceptor : IInterceptor { public void Intercept ( IInvocation invocation ) { var syncContext = SynchronizationContext.Current; SynchronizationContext.SetSynchronizationContext( null ); try { invocation.Proceed(); } finally { SynchronizationContext.SetSynchronizationContext(syncContext); } } } }

Now, all we need to do is this:

var service = new Service(); var wrappingService = ProxyWrapper.WrapService( typeof (IService), service); var viewModel = new MyViewModel(wrappingService); await viewModel.OnButtonClicked();

Using Dependency Injection

Ideally, you'd want your proxies configured/wrapped in your container. Most containers support intercepting/replacing services before they are given to constructors. I am using Microsoft's Microsoft.Extensions.DependencyInjection , which unfortunately doesn't support it (see this issue). So instead, I have to get creative when registering my services.

var services = new ServiceCollection(); services.AddTransient<MyViewModel>(); services.AddTransient( typeof (IService), provider => { var instance = ActivatorUtilities.CreateInstance(provider, typeof (Service)); return ProxyWrapper.WrapService( typeof (IService), instance; }); var provider = services.BuildServiceProvider(); var viewModel = provider.GetService<MyViewModel>(); await viewModel.OnButtonClicked();

Conclusion