【问题标题】:Synchronizing Events同步事件
【发布时间】:2011-06-09 16:44:52
【问题描述】:

我注意到,如果事件触发得太快,我的代码有时会变得不同步。我想知道是否有更好的方法。在正常情况下,DeviceOpenedEvent 在我在 TestDevice 方法中告诉线程到 WaitOne 后触发,但我看到在某些情况下,事件在线程有机会等待之前被触发。

    protected AutoResetEvent TestAutoResetEvent = new AutoResetEvent(false);
    public EventEnum WaitForEvent = EventEnum.None;

    bool TestDevice()
    {
        OpenDevice();

        WaitForEvent = EventEnum.DeviceOpened;
        TestAutoResetEvent.WaitOne();
        WaitForEvent = EventEnum.NoWait;

        //Continue with other tests
    }

    void DeviceOpenedEvent()
    {
        if (WaitForEvent == EventEnum.DeviceOpened)         
            TestAutoResetEvent.Set();                           
    }

正常情况下是这样的:

  1. 打开设备
  2. WaitOne()
  3. DeviceOpenedEvent 发生
  4. 设置()

这就是我有时看到的日志:

  1. 打开设备
  2. DeviceOpenedEvent 发生
  3. WaitOne() 基本上永远卡在这里

【问题讨论】:

  • 不应该 OpenDevice 方法指示设备何时打开(而不是 TestDevice 方法)?
  • OpenDevice 是一个异步方法调用。 TestDevice 对设备执行一系列操作,如打开、锁定、开机、关机。

标签: c# .net multithreading concurrency


【解决方案1】:

由于OpenDevice 是异步的(正如您在评论中提到的),它在与其调用者不同的线程中运行。有时它会在源代码中的下一行执行之前完成:

    OpenDevice(); // Async: may finish before the next line executes!
    WaitForEvent = EventEnum.DeviceOpened;

当这种情况发生时,DeviceOpenedEvent 不会做你想做的事,因为WaitForEvent 仍然是EventEnum.None

if (WaitForEvent == EventEnum.DeviceOpened)         
    TestAutoResetEvent.Set(); 

解决方案是更改代码,以便在保证以正确顺序运行的方法内发出完成信号。这是一个简单的实现,它删除了枚举并为您需要等待的每个事件使用单个等待句柄:

protected AutoResetEvent deviceOpenedEvent = new AutoResetEvent(false);
protected AutoResetEvent deviceLockedEvent = new AutoResetEvent(false);

bool TestDevice() {
    OpenDevice();
    // Do some unrelated parallel stuff here ... then
    deviceOpenedEvent.WaitOne();
    LockDevice();
    deviceLockedEvent.WaitOne();
}

void DeviceOpenedEvent() {
    deviceOpenedEvent.Set();                           
}

如果您控制OpenDevice,那就更容易了:完成后只需调用deviceOpened.Set()。您甚至可以更改 OpenDevice 以接受自动重置事件并在 TestDevice 内构建它,这将减少您遇到多线程错误的风险。

【讨论】:

  • 这种情况怎么不可能? 1. OpenDevice() 2. 异步回调 3. deviceOpenedEvent.WaitOne()
  • 这种情况是可能的,但这不是问题。当它发生时,deviceOpenedEvent.WaitOne 不会阻塞(正如 Fredrik 在他的回答中解释的那样),因为AutoResetEvent 一直保持信号状态,直到有东西“消耗”它。除非有另一个线程在等待事件,否则没问题(您可以使用ManualResetEvent 或其他信号机制来解决那个)。
【解决方案2】:

这应该不是问题。 documentation for AutoResetEvent 声明:

如果线程调用 WaitOne 而 AutoResetEvent 在有信号 状态,线程不会阻塞。

以下代码不会导致WaitOne阻塞,例如:

AutoResetEvent waitHandle = new AutoResetEvent(false);
waitHandle.Set();
waitHandle.WaitOne();
Console.WriteLine("After WaitOne");

【讨论】:

  • 它不会阻塞——除非有另一个线程在等待同一个事件,在这种情况下另一个线程将消耗信号并且事件将被重置。 @Robert,这里发生了这样的事情吗?
  • @Robert:有了这个更新,@Jeff 描述的情况听起来好像是有效的。是否有任何其他代码使用WaitOne 来等待同一AutoResetEvent 实例中的信号?
  • @Fredrick - 我认为我们都错过了这里的实际问题:等待句柄从未发出信号,因为OpenDevice(和DeviceOpenedEvent 方法)在下一行代码执行。
猜你喜欢
  • 2019-06-28
  • 2023-04-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-08
相关资源
最近更新 更多