【问题标题】:Waiting for an event to fire while using SemaphoreSlim使用 SemaphoreSlim 时等待事件触发
【发布时间】:2020-04-21 01:13:36
【问题描述】:

我可以像这样使用 SemaphoreSlim 来等待偶数触发:

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();
    }
}

但是,如果我需要先等待来自_object 的事件完成,然后再等待另一个事件,然后再调用_signal.Release(),该怎么办?像这样:

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();
    }
}

【问题讨论】:

  • 你能再创建一个信号量吗?或者在信号量上允许 2 个线程而不是一个,并使用 CurrentCount 来确保信号量在正确的位置等待?因此,在 Run() 中,在等待之前确保 _signal.CurrentCount == 0,并在 _object_OnConnected(object sender, EventArgs e) 中检查 _signal.CurrentCount == 1,然后再次等待,然后在 _object_OnWorkFinished(object sender, EventArgs e) 中释放和 _object_OnDisconnected(object sender, EventArgs e)
  • 仅供参考,事件的名称不以On 开头。例如,按钮将具有Click 事件,而不是OnClick 事件。当您看到 OnClick 方法时,它们通常是类用于引发 Click 事件的 protected void 方法。

标签: c# semaphore


【解决方案1】:

SemaphoreSlim 用于信号是可能的,但more common pattern is to use TaskCompletionSource<T> 使事件async 友好(即TAP)。一旦有了async-friendly 方法,您就可以更自然地组合它们。

我更喜欢将我的 TAP 包装器编写为扩展方法,例如:

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)
}

一旦有了 TAP 扩展方法,编写它们就容易多了:

public class MyClass
{
  private ObjectThatHasEvent _object;

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

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

【讨论】:

    【解决方案2】:

    根据我的评论,这样的东西有用吗?

    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();
        }
    }
    

    【讨论】:

    • 这似乎不起作用。线程永远不会继续超过第二个等待句柄,并且永远不会调用 _object_OnWorkFinished。
    • 查看更新,我认为您需要将 _signal.WaitAsync() 移到 dowork 上方。您需要在等待之后执行受信号量保护的工作。
    猜你喜欢
    • 2017-08-22
    • 2019-06-11
    • 1970-01-01
    • 1970-01-01
    • 2012-07-09
    • 1970-01-01
    • 2022-08-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多