Implementing IAsyncResult explicitly
Asked Answered
J

2

6

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.

Jag answered 1/1, 2009 at 23:4 Comment(0)
S
2
  • 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.

Sfumato answered 2/1, 2009 at 0:4 Comment(4)
WaitAny and WaitAll just need a WaitHandle, don't they?Jag
Because I am considering a case when WaitHandle and IsCompleted are available, but AsyncState is not. Edited the question to clarify this.Jag
I might be missing something, but isn't it trivial to support AsyncState, and cheap too?Sfumato
BTW, it seems you are implementing the functionality already provided by asynchronous delegates, see related question?Sfumato
Y
3

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.

Yorgo answered 2/1, 2009 at 2:2 Comment(4)
I wonder who came up with the idea of implementing IDisposable explicitly in an important class with such heavy implementations. After looking in Reflector, I don't see where AsyncResult disposes of its WaitHandle or where EndInvokeCalled is set. I need to think about it some more.Jag
In EndInvoke, presumably (the implementation of which is invisible to Reflector).Jag
@alexey_r, Check out FileStread.EndRead. The trick with Reflector is to go IAsyncResult->Derived Types. Pick a derived type, look for the real implementation of AsyncWaitHandle and do a find all ref. Eventually you'll find a place where it's disposed inside of an End*** method.Yorgo
Thanks! In my scenario, I want several threads to be able to get the WaitHandle and wait on it. ManualResetEvent doesn't work too well for this: bad things happen if my class finishes calculation, disposes it and then some thread decides to wait on it. (continued...)Jag
S
2
  • 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.

Sfumato answered 2/1, 2009 at 0:4 Comment(4)
WaitAny and WaitAll just need a WaitHandle, don't they?Jag
Because I am considering a case when WaitHandle and IsCompleted are available, but AsyncState is not. Edited the question to clarify this.Jag
I might be missing something, but isn't it trivial to support AsyncState, and cheap too?Sfumato
BTW, it seems you are implementing the functionality already provided by asynchronous delegates, see related question?Sfumato

© 2022 - 2024 — McMap. All rights reserved.