【问题标题】:How can I avoid having to pass around/store a delegate when using BeginInvoke and EndInvoke?使用 BeginInvoke 和 EndInvoke 时,如何避免传递/存储委托?
【发布时间】:2009-09-27 20:11:32
【问题描述】:

编辑:将实际问题移到顶部。

更新:找到了 Microsoft 的一个示例,最后添加了一些代码。

我的问题是:

  1. 在同一个委托实例上调用多个 BeginInvoke 调用是否安全,还是我必须为每个正在进行的方法调用构造一个新的委托实例?
  2. 如果我必须为每个实例构造新实例,有没有办法从 IAsyncResult 值中获取原始委托?
  3. 除了使用委托之外,还有其他更好的方法可以为我的类添加异步支持吗?

更多信息如下。

我正在为我的一个类添加异步支持,并认为我会做的很简单。

参加这门课:

public class Calculator
{
    public Int32 Add(Int32 a, Int32 b)
    {
        return a + b;
    }
}

我想我可以简单地这样做:

public class Calculator
{
    private delegate Int32 AddDelegate(Int32 a, Int32 b);
    public Int32 Add(Int32 a, Int32 b)
    {
        return a + b;
    }

    public IAsyncResult BeginAdd(Int32 a, Int32 b,
        AsyncCallback callback, Object obj)
    {
        return new AddDelegate(Add).BeginInvoke(a, b, callback, obj);
    }

    public Int32 EndAdd(IAsyncResult ar)
    {
        return new AddDelegate(Add).EndInvoke(ar);
    }
}

这不起作用,因为这两个方法都构造了自己的委托对象,并且 .EndInvoke 调用会检查我调用它的委托实例是否与我最初调用 BeginInvoke 的委托实例相同。

处理这种情况的最简单方法是将引用存储到变量中,如下所示:

public class Calculator
{
    private delegate Int32 AddDelegate(Int32 a, Int32 b);
    private AddDelegate _Add;

    public Calculator()
    {
        _Add = new AddDelegate(Add);
    }

    public Int32 Add(Int32 a, Int32 b)
    {
        return a + b;
    }

    public IAsyncResult BeginAdd(Int32 a, Int32 b,
        AsyncCallback callback, Object obj)
    {
        return _Add.BeginInvoke(a, b, callback, obj);
    }

    public Int32 EndAdd(IAsyncResult ar)
    {
        return _Add.EndInvoke(ar);
    }
}

请注意,我完全意识到允许同一类上的多个实例方法同时执行的问题,涉及共享状态等。


更新:我在 Microsoft 的 Asynchronous Delegates Programming Sample 上找到了这个示例。它显示将IAsyncResult 引用转换回AsyncResult 对象,然后我可以通过AsyncDelegate 属性获取原始委托实例。

这是一种安全的方法吗?

也就是说,下面的类可以吗?

public class Calculator
{
    private delegate Int32 AddDelegate(Int32 a, Int32 b);

    public Int32 Add(Int32 a, Int32 b)
    {
        return a + b;
    }

    public IAsyncResult BeginAdd(Int32 a, Int32 b, AsyncCallback callback, Object obj)
    {
        return new AddDelegate(Add).BeginInvoke(a, b, callback, obj);
    }

    public Int32 EndAdd(IAsyncResult ar)
    {
        AddDelegate del = (AddDelegate)((AsyncResult)ar).AsyncDelegate;
        return del.EndInvoke(ar);
    }
}

【问题讨论】:

    标签: c# asynchronous delegates


    【解决方案1】:

    编辑:如果您只是指委托本身 - 我认为您可以这样做:

    public Int32 EndAdd(IAsyncResult ar)
    {
        var d = (AddDelegate)((AsyncResult)ar).AsyncDelegate;
        return d.EndInvoke(ar);
    }
    

    您总是可以将其捕获到委托中;像这里的东西:Async without the Pain,它让你只使用Action回调(或Action<T>)。

    其他常见模式涉及回调事件,可能还有ThreadPool.QueueUserWorkItem;比IAsyncResult简单很多。


    将所有这些放在一起;这是一个示例,调用者和代码都不需要对IAsyncResult 感到压力:

    using System;
    using System.Runtime.Remoting.Messaging;
    public class Calculator
    {
        private delegate Int32 AddDelegate(Int32 a, Int32 b);
        public Int32 Add(Int32 a, Int32 b)
        {
            return a + b;
        }
    
        public IAsyncResult BeginAdd(Int32 a, Int32 b,
            AsyncCallback callback, Object obj)
        {
            return new AddDelegate(Add).BeginInvoke(a, b, callback, obj);
        }
    
        public Int32 EndAdd(IAsyncResult ar)
        {
            var d = (AddDelegate)((AsyncResult)ar).AsyncDelegate;
            return d.EndInvoke(ar);
        }
    }
    static class Program
    {
        static void Main()
        {
            Calculator calc = new Calculator();
            int x = 1, y = 2;
            Async.Run<int>(
                (ac,o)=>calc.BeginAdd(x,y,ac,o),
                calc.EndAdd, result =>
                {
                    Console.WriteLine(result());
                });
            Console.ReadLine();
        }
    }
    static class Async
    {
        public static void Run<T>(
        Func<AsyncCallback, object, IAsyncResult> begin,
        Func<IAsyncResult, T> end,
        Action<Func<T>> callback)
        {
            begin(ar =>
            {
                T result;
                try
                {
                    result = end(ar); // ensure end called
                    callback(() => result);
                }
                catch (Exception ex)
                {
                    callback(() => { throw ex; });
                }
            }, null);
        }
    }
    

    【讨论】:

    • 我需要返回值,所以 IAsyncResult 和代表的支持看起来很符合我的需要,但我会看看你的代码。请注意,您在那里定义的两个静态 RunAsync 方法具有相同的参数列表,因此它们不会编译。
    • 好的,我猜我在你写作的时候发现了通过 AsyncResult 进行的转换。所以这样做安全吗?这样的委托不会出现返回对象 not AsyncResult 对象的情况吗? (注意我不是在一般情况下询问 IAsyncResult,只是来自 delegate.BeginInvoke。)
    • 查看我对不依赖于 IAsyncResult 特定实现的方法的回答
    • @Marc-Gravell,为了避免(页面和代码部分)双滚动的必要性,将代码分成两部分可能会更好?
    • @Gennady Vanin--Геннадий Ванин 滚动很好。仅仅为了占据更多垂直空间而故意捏造样本更令人讨厌。
    【解决方案2】:

    我总是将调用它的委托实例存储为 BeginInvoke 的 AsyncState 的一部分,这样您就可以随时获取它,而无需依赖 IAsyncResult 的特定实现

    获得 IAsyncResult 后:

    IAsyncResult ar;    // this has your IAsyncResult from BeginInvoke in it
    
    MethodInvoker d = (MethodInvoker)ar.AsyncState;
    d.EndInvoke(ar);
    

    【讨论】:

    • 这意味着我将失去回调的额外参数能力。我可以牺牲那部分,现在只需要权衡不同的解决方案。
    • 如果你还想在其中存储其他东西,你可以在 AsyncState 中存储一个 object[]
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-03-16
    • 2011-02-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多