与过期异步调用操作

时间:2010-12-03 21:10:04

标签: c# .net multithreading asynchronous delegates

我有一个经典的异步消息调度问题。本质上,我需要异步调度消息,然后在调度完成时捕获消息响应。问题是,我似乎无法弄清楚如何让任何一个请求周期自我过期和短路。

以下是我目前使用的模式示例:

定义了调用委托

private delegate IResponse MessageDispatchDelegate(IRequest request);

使用回调发送消息

var dispatcher = new MessageDispatchDelegate(DispatchMessage);
dispatcher.BeginInvoke(requestMessage, DispatchMessageCallback, null);

发送消息

private IResponse DispatchMessage(IRequest request)
{
  //Dispatch the message and throw exception if it times out
}

将调度结果作为回复或例外

private void DispatchMessageCallback(IAsyncResult ar)
{
  //Get result from EndInvoke(r) which could be IResponse or a Timeout Exception
}

我无法弄清楚如何在 DispatchMessage 方法中干净地实现超时/短路过程。任何想法将不胜感激

2 个答案:

答案 0 :(得分:1)

        var dispatcher = new MessageDispatchDelegate(DispatchMessage);

        var asyncResult = dispatcher.BeginInvoke(requestMessage, DispatchMessageCallback, null);
        if (!asyncResult.AsyncWaitHandle.WaitOne(1000, false))
        {
             /*Timeout action*/
        }
        else
        {
            response = dispatcher.EndInvoke(asyncResult);
        }

答案 1 :(得分:0)

经过大量的讨论,我终于找到了原始问题的解决方案。首先,让我说我得到了很多很棒的回复,我测试了所有这些(用结果评论每个)。主要问题是所有提出的解决方案都导致死锁(导致100%超时情况的)或使其他Asyncronous进程同步。我不喜欢回答我自己的问题(第一次),但在这种情况下,我接受了StackOverflow常见问题解答的建议,因为我已经真正学到了自己的教训,并希望与之分享社区。

最后,我将提议的解决方案与将delagates调用到备用AppDomains中。这是一个更多的代码,它有点贵,但这可以避免死锁,并允许完全异步调用,这是我所需要的。这是比特......

首先我需要在另一个AppDomain中调用委托

/// <summary>
/// Invokes actions in alternate AppDomains
/// </summary>
public static class DomainInvoker
{
    /// <summary>
    /// Invokes the supplied delegate in a new AppDomain and then unloads when it is complete
    /// </summary>
    public static T ExecuteInNewDomain<T>(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));
        }
    }
}

第二我需要一些东西来协调所需参数的收集并收集/解析结果。这还将定义将在备用AppDomain

中异步调用的超时和工作进程

注意:在我的测试中,我扩展了调度工作器方法,以便随机花费大量时间来观察在超时和非超时情况下一切都按预期工作

public delegate IResponse DispatchMessageWithTimeoutDelegate(IRequest request, int timeout = MessageDispatcher.DefaultTimeoutMs);

[Serializable]
public sealed class MessageDispatcher
{
    public const int DefaultTimeoutMs = 500;

    /// <summary>
    /// Public method called on one more many threads to send a request with a timeout
    /// </summary>
    public IResponse SendRequest(IRequest request, int timeout)
    {
        var dispatcher = new DispatchMessageWithTimeoutDelegate(SendRequestWithTimeout);
        return DomainInvoker.ExecuteInNewDomain<Response>(dispatcher, request, timeout);
    }

    /// <summary>
    /// Worker method invoked by the <see cref="DomainInvoker.ExecuteInNewDomain<>"/> process 
    /// </summary>
    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;
    }

    /// <summary>
    /// Worker method to do the actual dispatch work while being monitored for timeout
    /// </summary>
    private IResponse DispatchMessage(IRequest request)
    {
        /* Do real dispatch work here */
        return new Response();
    }
}

第三次我需要一些东西来代替异步触发调度的实际内容

注意:这只是为了演示我需要的异步行为。实际上,上面的 First Second 项演示了备用线程上的超时行为的隔离。这只是演示了如何使用上述资源

public delegate IResponse DispatchMessageDelegate(IRequest request);

class Program
{
    static int _responsesReceived;

    static void Main()
    {
        const int max = 500;

        for (int i = 0; i < max; i++)
        {
            SendRequest(new Request());
        }

        while (_responsesReceived < 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);
    }
}

回想起来,这种方法会产生一些意想不到的后果。由于worker方法发生在备用AppDomain中,因此会为异常添加额外保护(虽然它也可以隐藏它们),允许您加载和卸载其他托管程序集(,如果需要 >),并允许您定义高度约束或专门的安全上下文。这需要更多的生产,但提供了回答我原始问题的框架。希望这有助于某人。