Make Synchrounous methods Asynchronous using Decorator or Interception


Sometimes when you are writing a piece of code, it doesn’t look necessary to be written as an asynchronous code. However, after a while we may need to make it asynchronous. The following code sample shows how to implement such scenarios using the Decorator design pattern and Dependency Injection, such that we don’t need to touch the client code to make it asynchronous.
Here is the interface for a Worker service which can be implemented either synchronously or asynchronously:
interface IWorker { void DoWork(object data); }
Suppose that the following code is our client which is calling the DoWork
method of IWorker
in its Run
method.
class Client { private readonly IWorker worker;
public Client(IWorker worker) { this.worker = worker; }
public void Run() { for (int i = 1; i <= 5; i++) { worker.DoWork(i); }
Console.WriteLine("End."); Console.ReadLine(); } }
The DoWork
method is called sequentially before the word “End.” gets printed on the screen. So in a synchronous implementation of IWorker the output would be something like this:
Here is the synchronous implementation of IWorker:
class SyncWorker : IWorker { public void DoWork(object taskNumber) { Thread.Sleep(1000); Console.WriteLine("Task {0} finished.", taskNumber); } }
Now if we need to make the Worker class run asynchronously, we would just need to create a decorator for the existing implementation:
class AsyncWorker : IWorker { private readonly IWorker innerWorker;
public AsyncWorker(IWorker innerWorker) { this.innerWorker = innerWorker; }
public void DoWork(object data) { ThreadPool.QueueUserWorkItem(o => innerWorker.DoWork(o), data); } }
In order to make our code run using the new implementation of IWorker, we just need to instruct our IoC container to do so. In this example we are using Ninject as our IoC container and the service bindings are included in a Ninject module named WorkerServiceRegistry
.
static void Main(string[] args) { using (var kernel = new StandardKernel()) { kernel.Load(new WorkerServiceRegistry(false)); var client = kernel.Get(); client.Run(); }
}
As the following code shows, we can even tell the WorkerServiceRegistry module whether we need a synchronous or an asynchronous implementation of IWorker
:
class WorkerServiceRegistry : NinjectModule { private readonly bool registerAsAsync;
public WorkerServiceRegistry(bool registerAsAsync) { this.registerAsAsync = registerAsAsync; }
public override void Load() { if (registerAsAsync) { Bind().To(); Bind().To().WhenInjectedInto(); } else { Bind().To(); } } }
In this example we have decoupled our client code from our concrete worker and also separated the concern of running the code asynchronously, that’s why we could easily intercept the sync worker and substitute it with an async version of it.
This approach can also be implemented using the Interception feature of your IoC container. In that case we will no longer need our decorator class. Instead, we will have to create an interceptor. It will look like this:
internal class AsyncInterceptor : IInterceptor { public void Intercept(IInvocation invocation) { ThreadPool.QueueUserWorkItem(o => invocation.Proceed()); } }
To wire up our interceptor, we need to introduce it to our IoC container. Here is the updated version of the Load() method of our WorkerServiceRegistry class which uses the interceptor instead of the SyncWorker decorator:
public override void Load() { if (registerAsAsync) { Bind().To().Intercept().With(); } else { Bind().To(); } }
For more information on how to use Interception in Ninject visit [this post](https://baharestani.com/2013/12/30/interception-with-ninject/ "Interception with Ninject").
Subscribe to my newsletter
Read articles from Daniel directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
