显式实现IAsyncResult接口

6
我通常不建议局部实现接口。然而,IAsyncResult 是一个特殊情况,因为它支持多种不同的用法模式。您使用/看到使用 AsyncState/AsyncCallback 模式的频率有多高?相比之下,仅调用 EndInvoke,使用 AsyncWaitHandle 或轮询 IsCompleted(yuck),您会使用哪种模式?
相关问题:检测 ThreadPool WorkItem 是否已完成/等待完成
考虑以下类(需要加锁):
public class Concurrent<T> {
    private ManualResetEvent _resetEvent;
    private T _result;

    public Concurrent(Func<T> f) {
        ThreadPool.QueueUserWorkItem(_ => {
                                         _result = f();
                                         IsCompleted = true;
                                         if (_resetEvent != null)
                                             _resetEvent.Set();
                                     });
    }

    public WaitHandle WaitHandle {
        get {
            if (_resetEvent == null)
                _resetEvent = new ManualResetEvent(IsCompleted);
            return _resetEvent;
        }

    public bool IsCompleted {get; private set;}
    ...

它有一个WaitHandle(懒惰地创建,就像IAsyncResult文档中描述的那样)和IsCompleted,但我没有看到AsyncState的合理实现({return null;}?)。所以它实现IAsyncResult是否有意义?请注意,Parallel Extensions库中的Task确实实现了IAsyncResult,但只有IsCompleted是隐式实现的。
2个回答

3

看起来你有几个问题。 让我们逐个解决。

延迟创建WaitHandle

是的,这是最正确的方法。 你应该以线程安全的方式完成这个操作,但是lazy是一种方法。

关键是处理WaitHandle的释放。 WaitHandle是IDisposable的基类,必须及时释放。 IAsycResult的文档没有涵盖这种情况。 最好的方法是在EndInvoke中实现此操作。 BeginInvoke的文档明确说明,对于每个BeginInvoke,必须有相应的EndInvoke(或BeginRead / EndRead)。 这是处理WaitHandle释放的最佳位置。

如何实现AsyncState?

如果查看返回IAsyncResult的标准BCL API,则其中大多数都使用状态参数。 这通常是从AsyncState返回的值(请参见Socket API的示例)。 对于返回IAsyncResult的任何API BeginInvoke样式的API,包含一个类型为object的状态变量是一个好习惯,但不是必需的。

如果没有状态变量,则返回null是可以接受的。

IsCompleted API

这将高度依赖于创建IAsyncResult的实现。 但是,您应该实现它。


我想知道是谁想出了在一个重要的类中明确实现IDisposable并且有如此复杂的实现的想法。在反编译器中查看后,我没有看到AsyncResult处置其WaitHandle的地方,也没有看到设置EndInvokeCalled的地方。我需要再考虑一下。 - Alexey Romanov
在EndInvoke中,假定(其实现对Reflector不可见)。 - Alexey Romanov
@alexey_r,请查看FileStread.EndRead。使用Reflector的技巧是进入IAsyncResult->Derived Types。选择一个派生类型,查找AsyncWaitHandle的真实实现,并进行查找所有引用。最终,您会找到在End***方法内部处置它的位置。 - JaredPar
谢谢!在我的情况下,我希望多个线程能够获取WaitHandle并等待它。ManualResetEvent对此不太适用:如果我的类完成计算、处理它,然后某个线程决定等待它,那么会发生一些糟糕的事情。(续...) - Alexey Romanov

2
  • 根据我的经验,仅仅调用EndInvoke而不等待或者先被回调很少有用。
  • 仅仅提供回调有时还不够,因为你的客户可能想要同时等待多个操作(WaitAny, WaitAll)。
  • 我从未轮询过IsCompleted,确实很糟糕!所以,你可以省略实现IsCompleted,但它非常简单,似乎不值得让你的客户感到惊讶。

因此,一个合理的异步可调用方法的实现应该真正提供一个完整实现的IAsyncResult。

顺便说一下,通常情况下你不需要自己实现IAsyncResult,只需返回Delegate.BeginInvoke返回的内容即可。参见System.IO.Stream.BeginRead的实现示例。


WaitAny和WaitAll只需要一个WaitHandle,是吗? - Alexey Romanov
因为我正在考虑一种情况,即WaitHandle和IsCompleted可用,但AsyncState不可用。编辑了问题以澄清这一点。 - Alexey Romanov
我可能错过了什么,但是支持AsyncState不是微不足道的吗?而且成本也很低吗? - user49572
顺便提一下,看起来你正在实现异步委托已经提供的功能,可以参考相关问题。 - user49572

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接