Sometimes when we humble programmers write software, we will want to do something that seems simple at a conceptual level, yet ends up being rather tricky due to one weird trick or some other hitch in the way the environment works.
Due to the maturity of both .Net and the WPF application framework, most programmers know how to keep their applications responsive. Initialize your UI elements, rely on view models and data-binding, and perform any long-running operations inside of a
Task to make sure you avoid the dreaded frozen or unresponsive window that we've all experienced from time to time.
However, this time around I needed to communicate between host and plugin AppDomains in a .Net application. Another developer on the team had already created a functional plugin framework, but I wanted to explore ways to encourage the use of the async techniques that have been available in the framework since Microsoft introduced the Task Parallel Library (TPL) and
await. Sadly, one of the primary functions of the
AppDomain in .Net is to provide safety and runtime isolation by preventing threads in one application space from communicating with threads in another. Further complicating the issue, anything related to the
Task objects (which provide simple abstractions of multithread processing) cannot be transferred across the domain barrier. Which means that simply doing this:
is only going to throw an error.
My master plan to encourage asynchronous programming in 3rd party plugins was foiled by a framework level safeguard mechanism! Though it turns out you can work around most programming roadblocks if you're motivated. In this case, I needed to first focus on the work-around, then I was able to rework some of the ugly parts and effectively hide them in a way that nobody will need to understand them.
Simple asynchronous workers
Searches across the internet, particularly the MSDN Forums and Stack Overflow, will show you several useful techniques allowing you to execute a task asynchronously using a proxy in the client domain to talk to the worker service in a remote/host/whatever domain. The general gist of the technique is to marshall a worker service across domain boundaries and utilize a result object to manage the creation of
Task objects that stay in the domain requesting the remote asynchronous work:
Behind the scene, the
AsyncResult object extends MarshalByRefObject and contains a
This technique works very well, and has resulted in several accepted answers whenever I've seen it. The main requirement of this trick is that the data going back and forth across the
AppDomain barrier be either a value type, or extend
MarshalByRefObject (which are typical requirements of anything going across the barrier). Since I'm going to be laying inter-domain communication in as a cornerstone of the application framework, I don't think that is an onerous requirement.
Unfortunately, as much as all of this works marvelously, it is not transparent to 3rd party developers.
My original objective had been to make an integration library that even I would want to use. I knew that this was not quite where things needed to be. The worker approach enabled processing, but at the cost of syntactic simplicity. It also required particular knowledge about the host environment, and that seemed like it should get baked in for free.
Having a working proof of concept allowed me to refocus on my overarching goals:
- Make the programming experience as seamless as possible for 3rd party developers.
- Encourage good programming techniques through the framework, rather than by defining a set of easily ignored guidelines and best practices.
So it seemed like a good time to hide the guts of the proxy-client/host-worker operation behind some slight-of-hand and a little bit of magic.
Nothing up my sleeve: utilizing Impromptu Interface to hide a dynamic proxy
A while back, I'd stumbled on an interesting library called Impromptu Interface. I didn't have any use for it at the time, so I tucked it into my 'Resources' browser bookmarks folder. As I was mulling over the problems presented in this project, the library once again jumped to the front of my brain.
At its core, Impromptu Interface lets you pass off a
dynamic or anonymous object as some previously defined interface, and this is exactly the functionality I wanted to exploit. We already strongly encourage the use of Interfaces rather than concrete service classes through our Inversion of Control systems, so this new seemed like a perfect fit to address the problem while playing to the strengths already present in our application and framework.
Why not use RealProxy?
I had looked into utilizing a
RealProxy implementation to do this, as it is supported directly in the core framework — however it didn't seem like a good fit given the way it expects you to return results. So I turned to my good friend,
DynamicObject, which operates off of .Net reflection types. Further, DynamicObject's
TryInvokeMember method played perfectly into the async worker mechanism detailed above.
It may be of particular interest is that Impromptu Interface utilizes
RealProxy under the hood for some of its operations, but it also handles all of the dirty business I had no interest in dealing with.
Ultimately, we may choose to switch to a
RealProxy implementation once we've fleshed out some other areas of the system.
Putting it together - a factory to generate interface proxies
So, after all this, I've got a grand plan that I'm going to make some sort of thing, pass it off as an interface, and then perform asynchronous work when an asynchronous method of that interface is called. Easy! Wait... no, probably not? At least our little factory and proxy class will make it look that way.
Items of note:
- ServiceLocator patterns are often bad programming, and this is indeed a bad use of a service locator. Were this an example of the final code, the proxy would utilize constructor injection to get a reference to
- A fair amount of reflection will occur each time a method is called on the proxy. The .Net framework is pretty speedy in terms of doing reflection, however this could be sped up by doing some code generation on first request and caching the resulting method. My implementation currently caches much of the metadata - sacrificing memory in lieu of repeatedly looping over Type member data.
- You would also want to implement the
DynamicObject, but those are both synchronous and not what we're talking about right now.
- TaskCompletionSource does not have a non-generic signature, so you need to detect when you have a
Taskwith no return type and handle it appropriately.
In this project we use Ninject, so each plugin domain will come pre-populated with bindings that allow it to call
ProxyFactory.CreateProxy<T>() when it encounters an interface that is not already known. Plugins need only inject the needed host (or external plugin) interface via constructor injection, and they'll have access to what they need.
By the time the above techniques are implemented, it will be fairly simple for a plugin to inject something like
IComplexCalculator from the host, fire off a long-running asynchronous calculation, and still have a responsive UI thread for both their plugin as well as the host shell.