If you have not used it yet, Signalr is a real-time web communication library which is now under the
ASP.NET banner. It is a very cool technology, which allows you to actively push data from a server application to your clients, without having to worry
too much about the particular transport mechanism by which the content is delivered - SignalR ideally uses WebSockets, but degrades gracefully to older
transport protocols (like AJAX long polling), if WebSocket connections are not supported by either the client or server. This is very powerful, since
it allows you to write a single implementation, focused on your high-level needs, and SignalR handles most of the nitty gritty work for you. In addition,
while SignalR can be used as something of a dumb pipe (although not that dumb, since you do get object->JSON serialization out of the box) via
PersistentConnection, the real power of SignalR lies in its Hubs concept. By connecting to a SignalR Hub, a client can call methods on the server,
both to execute commands, or to query for data. Similarly, the server can issue commands to the client by calling client-side callback methods.
This allows you to do some pretty cool things. Probably the canonical example is web-based instant-messaging/chat rooms, which is dead simple to
implement using SignalR. But really any kind web page that polls for new data on an interval is a good candidate for SignalR, like, say, real-time dashboards,
or a sports score ticker, maybe some classes of multiplayer games.
One scenario that SignalR does not support very nicely is when the server needs to query its clients for information and have that data returned to the Hub. As currently implemented,
a SignalR client can only define handlers for methods called by the Hub which are
Action<T,...> delegates, which have a
signature. So, while the code below will compile if it is included in a Hub method,
data is actually a
Task object, which is an asynchronous wrapper
for the client-side
Action. In short, there is no way to directly return a value from a client-side method back to the Hub.
var data = Clients.Client(SOME_ID).GetData()
Why would you want to do this kind of action using SignalR? In my case, I recently encountered a situation where a third-party library implemented a wrapper around a COM
application. This COM wrapper was implemented as a singleton, but in my particular case, I needed to execute multiple instances of the underlying COM application, in order
to connect to an external service with different user accounts, but, because the wrapper was a singleton, I could only make one connection from my main application process.
However, I could get around this limitation by spawning an additional process for each instance of the COM wrapper that was required (fortunately, it was simply a C# singleton,
and not limited by any kind of global mutex). While each child process could for the most part operate independently performing its responsibilities, the main application
needed to be able to issue commands to control the child processes, as well as retrieve information from them for various reporting features and to ensure consistency in
various aspects globally across the application.
I considered using a more traditional IPC method, like bidirectional sockets, or named pipes, but the simplicity and cleanness of using SignalR was more attractive to me
than rolling my own message-passing protocol and coding a message loop on both sides. At least for issuing commands to the child processes, implementing a SignalR Hub on my
main application to which the clients would connect was pretty quick and easy. Then I ran into the difficulty of how to get data back out of the child processes using SignalR.
In retrospect, I could have modified the code executing on the child processes into a more event-driven form, so that I could use the normal SignalR paradigm to push information
to the Hub in the main application, instead of querying the child processes for it. However, this would have involved a complete revision of the architecture of the application
(there were very similar components that ran within the main process, using the same interfaces and the bulk of the implementation), as well as necessitating an extensive amount
of caching in the main process.
Instead, I settled on the slightly crazy pattern illustrated here. In broad strokes, what I am doing is:
- A specially-tagged SignalR client owned by the main application calls a method on the Hub requesting a piece of data from a specific child process.
- The Hub resolves the requested child process to its SignalR connection Id, and calls a method to order the child process to assemble the requested data.
- The child process calculates the requested data, and pushes it to a different method on the Hub.
- The second Hub method stuffs the pushed data from the child process into a dictionary, keyed by the child process' identity.
- While this has been happening, the first Hub method has been blocking, waiting for the requested data to appear in the dictionary.
When the data appears in the dictionary, it is popped off, removing the key from the dictionary, preparing the dictionary for the next invocation. There is also
a timeout value, so that if there is some sort of interuption, a default value will be returned, rather than indefinitely blocking.
- Finally, the first Hub method returns the piece of data to the main application.
The code for this example is available on GitHub at https://github.com/ericrrichards/crazy-signalr-ipc