Search code examples
c#wcfasynchronoustaskcompletionsource

Waiting for events asynchronously in C#


I'm working on a WCF service/client trying to figure out how to replace a ManualResetEvent with something that won't block the caller thread.
Most important is that await client.CloseAsync() won't be called until the FinishedEventReceived event has been fired.

I've looked at using a TaskCompletionSource but I'm somewhat unsure how that would work in this case.

I know the code is a bit ugly and totally defeats the purpose of using asynchrounously programming, my apoligies.
Any ideas?

private async Task CallServiceMethodAndWaitForEvent()
{
    var mre = new ManualResetEvent(true);

    var client = new AwesomeClient();
    client.FinishedEventReceived += (s, e) =>
    {
        // Do something with the result, this event is only fired once.
        mre.Set();
    };
    client.UpdateEventReceived += (s, e) =>
    {
        // This even can fire several times before the finished event.
    };

    try
    {
        var parameters = new Parameters()
        {
            SomeParameter = "Test123",
            TestAmount = 10000,
        };

        var errors = await client.DoWorkAsync(parameters);
        Debug.WriteLine(errors);

        mre.WaitOne(TimeSpan.FromSeconds(20));
        await client.CloseAsync();
    }
    catch (FaultException ex)
    {
    }
    catch (Exception)
    {
        client.Abort();
    }
}

Solution

  • Probably the simplest way to do what you want would be to replace the ManualResetEvent with - as you mentioned - a TaskCompletionSource. For example:

    var tcs = new TaskCompletionSource<int>();
    
    var client = new AwesomeClient();
    client.FinishedEventReceived += (s, e) =>
    {
        // Do something with the result, this event is only fired once.
        tcs.SetResult(42); // number here is a dummy, since you only want Task
    };
    
    ...
    
    await tcs.Task;
    await client.CloseAsync();
    

    Note that the timeout aspect is harder; a common approach there is to use Task.Delay as a fallback, and Task.WhenAny, i.e.

    var timeout = Task.Delay(timeoutInterval);
    if (timeout == await Task.WhenAny(timeout, tcs.Task))
        throw new TimeoutException();