Miguel
Miguel

Reputation: 141

Ordered execution of methods given they haven't been fired in order

I have two asynchronous methods (let's call them AAsync() and BAsync()) that are intended to run one after the other one. This is because method AAsync() prepares the context for method BAsync() to act properly. For each call of AAsync() there's a correspondent call of the method BAsync().

These methods can be fired in the following order (as an example):

A1A2B1B2A3B3B4A4, where A stands for AAsync, B for BAsync, and the subindex indicates the call number of the method.

Given that it will always be that method AAsynci will be called after method AAsynci-1 and before AAsynci+1 (same applies for BAsync), and that a call AAsynci has a correspondant call BAsynci, I would like to know which is the best way to, given a disordered firing of the methods (like the one in the example above), execute the calls in the following order:

A1B1A2B2A3B3A4B4 ...

I have solved this using two FIFO Semaphores and two TaskCompletionSource (see below), but I have the feeling that this can be solved in a smarter way. Does anybody know a nicer solution?

TaskCompletionSource TCS_A;
TaskCompletionSource TCS_B;

FIFOSemaphore MutexA = new FIFOSemaphore(1);
FIFOSemaphore MutexB = new FIFOSemaphore(1);

async void AAsync()
{
    await MutexA.WaitAsync();

    if (TCS_B!= null)
        await TCS_B.Task;

    TCS_B = new TaskCompletionSource<bool>();

    // Do intended stuff...

    TCS_A?.TrySetResult(true);

    MutexA.Release();
}

async void BAsync()
{
    await MutexB.WaitAsync();

    if (TCS_A!= null)
        await TCS_A.Task;
            
    TCS_A = new TaskCompletionSource<bool>();

    // Do intended stuff...

    TCS_B?.TrySetResult(true);

    MutexB.Release();
}

Upvotes: 1

Views: 80

Answers (1)

Theodor Zoulias
Theodor Zoulias

Reputation: 43525

My suggestion is to use two Channel<T> instances. The channel A should receive the arguments passed to the A event handler, and the channel B should receive the arguments passed to the B event handler. Finally perform an asynchronous loop that takes one item from the channel A and one from the channel B ad infinitum:

Channel<EventArgs> _channelA = Channel.CreateUnbounded<EventArgs>();
Channel<EventArgs> _channelB = Channel.CreateUnbounded<EventArgs>();

void EventA_Handler(EventArgs e)
{
    _channelA.Writer.TryWrite(e);
}

void EventB_Handler(EventArgs e)
{
    _channelB.Writer.TryWrite(e);
}

async void EventHandlerLoop()
{
    while (true)
    {
        EventArgs a = await _channelA.Reader.ReadAsync();
        // Do intended stuff...
        EventArgs b = await _channelB.Reader.ReadAsync();
        // Do intended stuff...
    }
}

The method EventHandlerLoop should be invoked during the initialization of the class, to start the wheel rolling.

If you are not familiar with the channels, you could take a look at this tutorial.

Upvotes: 1

Related Questions