views:

190

answers:

3

Edit: Moved the actual question to the top.

Update: Found an example by Microsoft, tucked on some more code at the end.

My questions are these:

  1. Is it safe to call multiple BeginInvoke calls on the same delegate instance, or do I have to construct a new delegate instance for each in-flight method call?
  2. If I have to construct new instances for each, is there some way to get hold of the original delegate out of the IAsyncResult value?
  3. Is there some other, better, way to add asynchronous support to my class than using delegates?

More info follows.

I am adding asynchronous support to a class of mine, and thought I'd do it simple.

Take this class:

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

I thought I could simply do this:

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

This doesn't work, as the two methods each construct their own delegate object, and the .EndInvoke call checks to see if the delegate instance i call it on is the same as the one I originally called BeginInvoke on.

The simplest way to handle this would be to just store a reference into a variable, like this:

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

Note that I'm fully aware of problems with allowing multiple instance methods on the same class to execute at the same time, with regards to shared state, etc.


Update: I found this example here by Microsoft on Asynchronous Delegates Programming Sample. It shows casting the IAsyncResult reference back to an AsyncResult object, and then I can get the original delegate instance through the AsyncDelegate property.

Is this a safe approach?

In other words, is the following class fine?

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);
    }
}
+2  A: 

Edit: if you just mean the delegate itself - I think you can just do:

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


You could always capture it into the delegate; something like here: Async without the Pain, which lets you just use an Action callback (or Action<T>).

Other common patterns involve events for the callback, and perhaps ThreadPool.QueueUserWorkItem; a lot simpler than IAsyncResult.


Putting all this together; here's an example where neither the caller nor the code needs to get stressed about 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);
    }
}
Marc Gravell
I need returned values, so IAsyncResult and the support of the delegates looks spot on for my needs, but I'll look at your code. Note that the two static RunAsync methods you have defined there has the same parameter list, so they won't compile.
Lasse V. Karlsen
Ok, guess I found that cast through AsyncResult while you were writing. So this is safe to do so? There's not going to be a situation with delegates like this where the returned object is *not* an AsyncResult object? (note I'm not asking about IAsyncResult in the general case, just from delegate.BeginInvoke.)
Lasse V. Karlsen
See my answer for a method that doesnt depend on a specific implementation of IAsyncResult
thecoop
+1  A: 
SwDevMan81
+2  A: 

I always store the delegate instance I called it on as part of the AsyncState of a BeginInvoke, that way you can always get it without having to rely on a specific implementation of IAsyncResult

Once you've got your IAsyncResult:

IAsyncResult ar;    // this has your IAsyncResult from BeginInvoke in it

MethodInvoker d = (MethodInvoker)ar.AsyncState;
d.EndInvoke(ar);
thecoop
This means I'll lose the extra parameter ability of the callback. I can sacrifice that part, just need to weigh the different solutions up against each other now.
Lasse V. Karlsen
You can store an object[] in the AsyncState if you want to store other stuff in there as well
thecoop