Question: I can use a SemaphoreSlim to wait for an even to fire like this:

```
public class MyClass
{
    private SemaphoreSlim _signal;
    private ObjectThatHasEvent _object;

    public MyClass()
    {
        _signal = new SemaphoreSlim(0, 1);
        _object = new ObjectThatHasEvent();
        _object.OnEventFired += _object_OneEventFired;
    }

    public asnyc void Run()
    {
        _object.DoStuffAndFireEventAfterwards();
        _signal.WaitAsync();
    }

    private void _object_OnEventFired(object sender, EventArgs e)
    {
        _signal.Release();
    }
}

```

But what about if I need to wait for an event from `_object` to finish first and then another event before calling `_signal.Release()`? Like so:

```
public class MyClass
{
    private SemaphoreSlim _signal;
    private ObjectThatHasEvent _object;

    public MyClass()
    {
        _signal = new SemaphoreSlim(0, 1);
        _object = new ObjectThatHasEvent();
        _object.OnConnected += _object_OnConnected;
        _object.OnWorkFinished += _object_OnWorkFinished;
        _object.OnDisconnected += _object_OnDisconnected;            
    }

    public async Task Run()
    {
        _object.Connect();
        await _signal.WaitAsync();
    }

    private void _object_OnConnected(object sender, EventArgs e)
    {
        _object.DoWork();
        //How to wait for work finished here?
        _object.Disconnect();
    }

    private void _object_OnWorkFinished(object sender, EventArgs e)
    {
        //Only disconnect after this has finished...
    }

    private void _object_OnDisconnected(object sender, EventArgs e)
    {
        _signal.Release();
    }
}

```

Answer: 
Using `SemaphoreSlim` for a signal is possible, but the [more common pattern is to use `TaskCompletionSource<T>`](https://learn.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/interop-with-other-asynchronous-patterns-and-types#tasks-and-the-event-based-asynchronous-pattern-eap) to make the events `async`-friendly (i.e., TAP). Once you have `async`-friendly methods, you can combine them much more naturally.

I prefer to write my TAP wrappers as extension methods, something like:

```
public static class ObjectThatHasEventExtensions
{
  public static Task ConnectAsync(this ObjectThatHasEvent self)
  {
    // TODO: this wrapper does not handle connection errors.
    var tcs = new TaskCompletionSource<object>();
    EventHandler handler = null;
    handler = (sender, args) =>
    {
      self.OnConnected -= handler;
      tcs.TrySetResult(null);
    };
    self.OnConnected += handler;
    self.Connect();
    return tcs.Task;
  }

  public static Task DoWorkAsync(this ObjectThatHasEvent self)
  {
    // TODO: this wrapper does not handle work errors.
    var tcs = new TaskCompletionSource<object>();
    EventHandler handler = null;
    handler = (sender, args) =>
    {
      self.OnWorkFinished -= handler;
      tcs.TrySetResult(null);
    };
    self.OnWorkFinished += handler;
    self.DoWork();
    return tcs.Task;
  }

  // (same pattern for DisconnectAsync)
}

```

Once you have TAP extension methods, composing them is much, much easier:

```
public class MyClass
{
  private ObjectThatHasEvent _object;

  public MyClass()
  {
    _object = new ObjectThatHasEvent();
  }

  public async Task Run()
  {
    await _object.ConnectAsync();
    await _object.DoWorkAsync();
    await _object.DisconnectAsync();
  }
}

```
As per my comment, would something like this work?

```
public class MyClass
{
    private SemaphoreSlim _signal;
    private ObjectThatHasEvent _object;

    public MyClass()
    {
        _signal = new SemaphoreSlim(2, 2);
        _object = new ObjectThatHasEvent();
        _object.OnConnected += _object_OnConnected;
        _object.OnWorkFinished += _object_OnWorkFinished;
        _object.OnDisconnected += _object_OnDisconnected;            
    }

    public async Task Run()
    {
        if (_signal.CurrentCount == 2) // Make sure no other connections exist (still 2 threads available)
        {
            await _signal.WaitAsync();
            _object.Connect();
        }
    }

    private void _object_OnConnected(object sender, EventArgs e)
    {
        if (_signal.CurrentCount == 1) //Only do work if we've connected
        {
            await _signal.WaitAsync();
            _object.DoWork();
            _object.Disconnect();
        }
    }

    private void _object_OnWorkFinished(object sender, EventArgs e)
    {
        _signal.Release();
    }

    private void _object_OnDisconnected(object sender, EventArgs e)
    {
        _signal.Release();
    }
}

```