0

Say I have the following method that subscribes to an event. A callback is called when the event occurs. I would like to prevent my method to return until the callback is called, or after 10 seconds has passed.

public async Task<string> GetImportantString()
{
    string importantResult = null;
    await SubscribeToEvent("some event", async (message) =>
    {
        importantResult = message; // When "some event" happens, callback is called and we can set importantResult 
    }

    return message; // Only return when the callback is called, or 10 seconds have passed
}

The signature for SubscribeToEvent() is as follows:

public Task SubscribeToEvent(string event, Action<string> handler);

The way I would use method GetImportantString() is as follows:

public void SomeMethod() 
{
    // Do some things
    var importantString = await GetImportantString();
   // Do other things
}

The problem is that I cannot find a way to not return from GetImportantString() until the callback has been called. Ideally, I would like to wait until the callback has called for up to 10 seconds and return an error if the callback was not called within 10 seconds. How can I suspend the execution of GetImportantString() until a callback is called?

ThomasFromUganda
  • 380
  • 3
  • 17
  • The whole point of async in `GetImportantString` would be to not block the caller thread. Normally, if that is also awaited, then execution only continues after `SubscribeToEvent` returns – Charlieface Feb 07 '21 at 16:55
  • A better signature for the `SubscribeToEvent` method would be: `public Task SubscribeToEvent(string event, Func> handler);`. Your current signature does not allow a proper cooperation with an async delegate. The `async (message) =>` lambda in your example is `async void`, which is [something to avoid](https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming#avoid-async-void). – Theodor Zoulias Feb 07 '21 at 17:53
  • Btw the `async (message) =>` lambda should probably generate a warning in Visual Studio, about an `async` method that lacks an `await` operator. – Theodor Zoulias Feb 07 '21 at 17:58

1 Answers1

4

Take a look at this:

public async Task<string> GetImportantString()
{
    string importantResult = null;
    using (var sph = new SemaphoreSlim(0, 1))
    {
        await SubscribeToEvent("some event", async (message) =>
        {
            importantResult = message; // When "some event" happens, callback is called and we can set importantResult 
            sph.Release();
        });

        var t = sph.WaitAsync();

        if (await Task.WhenAny(t, Task.Delay(10000)) == t)
        {
            return importantResult;
        }
    }
    throw new TimeoutException(); // whatever you want to do here
}

We use a SemaphoreSlim to signal when the string is set.

At that point, we await on Task.WhenAny which lets us know when either the Semaphore releases or a delay-task elapses. If the Semaphore releases, we can safely assume the string has been set and return it.

Andy
  • 12,859
  • 5
  • 41
  • 56
  • Thanks for the reply! Unfortunately, the `Task` returned by `SubscribeToEvent()` does not indicate if the callback was called, it only indicates if you have subscribed to the event. Currently, the `Task` returned by `SubscribeToEvent` resolves almost instantly, but the actual callback is called 5 seconds later as the event takes much longer to happen. – ThomasFromUganda Feb 07 '21 at 16:59
  • @ThomasFromUganda -- try that out -- I haven't tested it. – Andy Feb 07 '21 at 17:05
  • 1
    That solution never crossed my mind. Thanks! – ThomasFromUganda Feb 07 '21 at 17:07
  • 1
    @TheodorZoulias -- Good catch -- i had just gotten out of bed and my head was not in the right space yet :) Thanks! Fixed. – Andy Feb 07 '21 at 18:07
  • The line `sph.Release();` may produce an unhandled `ObjectDisposedException` – Ackdari Feb 07 '21 at 19:11