Pete
Pete

Reputation: 6723

Consolidating callbacks

This isn't so much a language specific issue, but for my particular case, I'm using C#. (Edit: Before reading further, the selected answer to this question is language specific to C#)

I have two asynchronous service calls that I need to complete and then collect the information from those two callbacks. Because of the nature of the code generators, I basically end up with methods that I call that have a "-Completed" event handler that gets called with the results of the service calls.

So, in this case, I have two separate async calls collecting data from two different sources. When they're both complete, I want to combine the data and then continue normal workflow.

So, in a C# style of pseudo-code:

MyAsyncServiceClient1 masc1 = new MyAsyncServiceClient1();
MyAsyncServiceClient2 masc2 = new MyAsyncServiceClient2();

masc1.MyCallCompleted += new EventHandler<MyCallCompletedEventArts>((sender, e) =>
{
    .. collect some data ..
});

masc2.MyCallCompleted += new EventHandler<MyCallCompletedEventArts>((sender, e) =>
{
    .. collect some more data ...
});

masc1.MyCall();
masc2.MyCall();

So my question is, is there a general pattern for consolidating asynchronous callbacks like this so that I can collect the data from both callbacks and then perform some action with the combined data? I mean, I know there are ways of doing this. I've devised ways in the past, but as I do more and more async programming, I find that this is becoming a common pattern and I want to see if I can find a more elegant, generalized solution than the custom solutions I continue to come up with.

Upvotes: 2

Views: 79

Answers (1)

Servy
Servy

Reputation: 203823

What we can do is write a method to turn your event into a Task. (Here is the method for one of your types; you'd need to create such a method for each of your events, unless both types implement an interface defining this event.) Having a task allows for composition, aggregate operations, etc. that make problems like this much easier to solve.

public static Task<MyCallCompletedEventArts> CallAsync(
    this MyAsyncServiceClient1 client)
{
    var tcs = new TaskCompletionSource<MyCallCompletedEventArts>();
    EventHandler<MyCallCompletedEventArts> handler = null;
    handler = (_, args) =>
    {
        tcs.TrySetResult(args);
        client.MyCallCompleted -= handler;
    };
    client.MyCallCompleted += handler;
    client.MyCall();
    return tcs.Task;
}

(Note we jump through some hoops to make sure our handler is removed after the first time it fires. You can simplify the code a fair bit if that happens to be unnecessary in your case.)

Now you can write:

MyAsyncServiceClient1 masc1 = new MyAsyncServiceClient1();
MyAsyncServiceClient2 masc2 = new MyAsyncServiceClient2();

var results = await Task.WhenAll(masc1.CallAsync(), masc2.CallAsync());
DoSomethingWithFirstClientsArgs(results[0]);

Upvotes: 3

Related Questions