How to use EndInvoke when events/delegates called are not your responsibility

1.7k views Asked by At

I currently have a class that receives information constantly from an API. When its received that information it fires an event/delegate which other classes can subscribe to.

Because I don't want to block the thread the class is running on I use delegate.BeginInvoke to fire the event.

eg.

    object UpdateLock=new object();
    List<Action<object, UpdateArgs>> UpdateSubscribersList = new List<Action<object, UpdateArgs>>();
    public void UpdateSubscribe(Action<object, UpdateArgs> action)
    {
        lock (UpdateLock)
        {
            UpdateSubscribersList.Add(action);
        }
    }
    public bool UpdateUnSubscribe(Action<object, UpdatePortfolioArgs> action)
    {
        lock (UpdateLock)
        {
            return UpdateSubscribersList.Remove(action);
        }
    }
    public virtual void onUpdate(UpdateArgs args)
    {
        lock (UpdateLock)
        {
            foreach (Action<object, UpdateArgs> action in UpdateSubscribersList)
            {
                action.BeginInvoke(args, null, null);
            }
        }
    }

This is just an example. Don't worry about the use of the list rather than a multicast delegate - there's some other code which caused me to use that.

Anyway I have two questions:

1) where should I put EndInvoke so it doesn't block the thread? I believe this question has already been asked, but I'm not so clear on it, and anyway it's not my main question.

2) The main purpose of EndInvoke is to clean up the thread and handle exceptions. Now cleaning up the thread is fine, but how can I handle exceptions? I have no idea which code will be called by these delegates, and that code is not my responsibility. So is there anyway I can get the subscriber to these events to have to deal with the clean up code and the exceptions, rather than my class? Or is their anyway that he can access anything returned by EndInvoke at all, so that if he wants to he can deal with any exceptions?

1

There are 1 answers

8
Fildor On BEST ANSWER

I think this pattern should work for you.

public void StartAsync(Action a)
{
    a.BeginInvoke(CallBack, a); // Pass a Callback and the action itself as state

    // or a.BeginInvoke(Callback, null); then see alternative in Callback
}

private void CallBack(IAsyncResult ar)
{
    // In the callback you can get the delegate
    Action a = ar.AsyncState as Action;

    // or
    // Action a = ((AsyncCallback)ar).AsyncDelegate as Action

    try
    {
        // and call EndInvoke on it.
        a?.EndInvoke(ar); // Exceptions will be re-thrown when calling EndInvoke
    }
    catch( Exception ex )
    {
        Log.Error( ex, "Exception in onUpdate-Action!" );
    }
}

You should be able to adapt this to your Action<object, UpdateArgs> delegate.

I used StartAsync just for brevity. This would be inside the for loop of your onUpdate Method, of course.

Mind that Callback will be called on the Async-Thread! If you want to update GUI elements here, you'll need to marshal that back to the GUI-Thread.


If you add an event to your class

public event EventHandler<Exception> OnError;

you can publish those exceptions :

catch( Exception ex )
{
    Log.Error( ex, "Exception in onUpdate-Action!" );
    OnError?.Invoke( a, ex );
}