Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>After lots of head-scratching I was finally able to find a solution for my original question. First off, let me say I got a lot of great responses and I tested all of them (<em>commenting each with the results</em>). The main problems were that all the proposed solutions led to either dead-locks (<em>leading to 100% timeout scenario's</em>) or made an otherwise Asyncronous process syncronous. I don't like answering my own question (<em>first time ever</em>), but in this case I took the advice of the StackOverflow FAQ since I've truely learned my own lesson and wanted to share it with the community.</p> <p>In the end, I combined the proposed solutions with the invocation of delagates into alternate AppDomains. It's a bit more code and it's a little more expensive, but this avoids the dead-locks and allows fully asyncronous invocations which is what I required. Here are the bits...</p> <p><strong>First</strong> I needed something to invoke a delegate in another AppDomain</p> <pre><code>/// &lt;summary&gt; /// Invokes actions in alternate AppDomains /// &lt;/summary&gt; public static class DomainInvoker { /// &lt;summary&gt; /// Invokes the supplied delegate in a new AppDomain and then unloads when it is complete /// &lt;/summary&gt; public static T ExecuteInNewDomain&lt;T&gt;(Delegate delegateToInvoke, params object[] args) { AppDomain invocationDomain = AppDomain.CreateDomain("DomainInvoker_" + delegateToInvoke.GetHashCode()); T returnValue = default(T); try { var context = new InvocationContext(delegateToInvoke, args); invocationDomain.DoCallBack(new CrossAppDomainDelegate(context.Invoke)); returnValue = (T)invocationDomain.GetData("InvocationResult_" + invocationDomain.FriendlyName); } finally { AppDomain.Unload(invocationDomain); } return returnValue; } [Serializable] internal sealed class InvocationContext { private Delegate _delegateToInvoke; private object[] _arguments; public InvocationContext(Delegate delegateToInvoke, object[] args) { _delegateToInvoke = delegateToInvoke; _arguments = args; } public void Invoke() { if (_delegateToInvoke != null) AppDomain.CurrentDomain.SetData("InvocationResult_" + AppDomain.CurrentDomain.FriendlyName, _delegateToInvoke.DynamicInvoke(_arguments)); } } } </code></pre> <p><strong>Second</strong> I needed something to orchestrate collection of the required parameters and collect/resolve the results. This will also define the timeout and worker processes which will be called asyncronously in an alternate AppDomain</p> <p><em>Note: In my tests, I extended the dispatch worker method to take random amounts of time to observe that everything worked as expected in both timeout and non-timeout cases</em> </p> <pre><code>public delegate IResponse DispatchMessageWithTimeoutDelegate(IRequest request, int timeout = MessageDispatcher.DefaultTimeoutMs); [Serializable] public sealed class MessageDispatcher { public const int DefaultTimeoutMs = 500; /// &lt;summary&gt; /// Public method called on one more many threads to send a request with a timeout /// &lt;/summary&gt; public IResponse SendRequest(IRequest request, int timeout) { var dispatcher = new DispatchMessageWithTimeoutDelegate(SendRequestWithTimeout); return DomainInvoker.ExecuteInNewDomain&lt;Response&gt;(dispatcher, request, timeout); } /// &lt;summary&gt; /// Worker method invoked by the &lt;see cref="DomainInvoker.ExecuteInNewDomain&lt;&gt;"/&gt; process /// &lt;/summary&gt; private IResponse SendRequestWithTimeout(IRequest request, int timeout) { IResponse response = null; var dispatcher = new DispatchMessageDelegate(DispatchMessage); //Request Dispatch var asyncResult = dispatcher.BeginInvoke(request, null, null); //Wait for dispatch to complete or short-circuit if it takes too long if (!asyncResult.AsyncWaitHandle.WaitOne(timeout, false)) { /* Timeout action */ response = null; } else { /* Invoked call ended within the timeout period */ response = dispatcher.EndInvoke(asyncResult); } return response; } /// &lt;summary&gt; /// Worker method to do the actual dispatch work while being monitored for timeout /// &lt;/summary&gt; private IResponse DispatchMessage(IRequest request) { /* Do real dispatch work here */ return new Response(); } } </code></pre> <p><strong>Third</strong> I need something to stand-in for the actual thing that is asyncronously triggering the dispatches</p> <p><em>Note: This is just to demonstrate the asyncronous behaviours I required. In reality, the <strong>First</strong> and <strong>Second</strong> items above demonstrate the isolation of timeout behaviours on alternate threads. This just demonstrates how the above resources are used</em></p> <pre><code>public delegate IResponse DispatchMessageDelegate(IRequest request); class Program { static int _responsesReceived; static void Main() { const int max = 500; for (int i = 0; i &lt; max; i++) { SendRequest(new Request()); } while (_responsesReceived &lt; max) { Thread.Sleep(5); } } static void SendRequest(IRequest request, int timeout = MessageDispatcher.DefaultTimeoutMs) { var dispatcher = new DispatchMessageWithTimeoutDelegate(SendRequestWithTimeout); dispatcher.BeginInvoke(request, timeout, SendMessageCallback, request); } static IResponse SendRequestWithTimeout(IRequest request, int timeout = MessageDispatcher.DefaultTimeoutMs) { var dispatcher = new MessageDispatcher(); return dispatcher.SendRequest(request, timeout); } static void SendMessageCallback(IAsyncResult ar) { var result = (AsyncResult)ar; var caller = (DispatchMessageWithTimeoutDelegate)result.AsyncDelegate; Response response; try { response = (Response)caller.EndInvoke(ar); } catch (Exception) { response = null; } Interlocked.Increment(ref _responsesReceived); } } </code></pre> <p>In retrospect, this approach has some unintended consequences. Since the worker method occurs in an alternate AppDomain, this adds addition protections for exceptions (<em>although it can also hide them</em>), allows you to load and unload other managed assemblies (<em>if required</em>), and allows you to define highly constrained or specialized security contexts. This requires a bit more productionization but provided the framework to answer my original question. Hope this helps someone.</p>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload