Implementing IAsyncResult explicitly

2019-04-08 00:14发布

问题:

I am generally wary of implementing interfaces partially. However, IAsyncResult is a bit of a special case, given that it supports several quite different usage patterns. How often do you use/see used the AsyncState/AsyncCallback pattern, as opposed to just calling EndInvoke, using AsyncWaitHandle, or polling IsCompleted (yuck)?

Related question: Detecting that a ThreadPool WorkItem has completed/waiting for completion.

Consider this class (very approximate, locking needed):

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;}
    ...

It has WaitHandle (lazily created, just as described in IAsyncResult documentation) and IsCompleted, but I don't see a sensible implementation for AsyncState ({return null;}?). So does it make sense for it to implement IAsyncResult? Note that Task in the Parallel Extensions library does implement IAsyncResult, but only IsCompleted is implemented implicitly.

回答1:

  • In my experience, just calling EndInvoke without either waiting or being called back first is rarely useful
  • Just providing callbacks is sometimes not enough, as your clients might want to wait for multiple operations at once (WaitAny, WaitAll)
  • I've never polled IsCompleted, yuck indeed! So, you could save the implementation of IsCompleted, but it's so simple that it doesn't seem to be worth to potentially astonish your clients.

So, a reasonable implementation for an asynchronously callable method should really provide a fully implemented IAsyncResult.

BTW, you often don't need to implement IAsyncResult yourself, just return what is returned by Delegate.BeginInvoke. See the implementation of System.IO.Stream.BeginRead for an example.



回答2:

It seems like you have a couple of questions. Let's handle them individually

Creating WaitHandle lazily

Yes this is the most correct approach. You should do this in a thread safe manner but lazy is the way.

The trick though is disposing of the WaitHandle. WaitHandle is a base of IDisposable and must be disposed of in a timely fashion. The documentation for IAsycResult does not cover this case. The best way to do this is in EndInvoke. The documentation for BeginInvoke explicitly states that for every BeginInvoke, there must be a corresponding EndInvoke (or BeginRead/EndRead). This is the best place in which to dispose of the WaitHandle.

How should AsyncState be implemented?

If you look at the standard BCL API's which return an IAsyncResult, most of them take a state parameter. This is typically the value that is returned from AsyncState (see the Socket API's for an example). It is a good practice to include a state variable typed as object for any API BeginInvoke style API which returns an IAsyncResult. Not necessary but good practice.

In the abscence of a state variable, returning null is acceptable.

IsCompleted API

This will be highly dependent on the implementation which creates the IAsyncResult. But yes, you should implement it.