【问题标题】:C#: Invoke Event from Locked BlockC#:从锁定块调用事件
【发布时间】:2010-12-28 06:35:59
【问题描述】:

我通常听说最好在调用事件侦听器之前解锁任何锁以避免死锁。但是,由于lock {} 块在 C# 中由同一线程可重入,是否可以从锁定的块中调用事件,还是需要制作相关状态数据的副本并在锁定块外调用事件?

如果不是,请举例说明何时从 lock {} 块中调用事件会出现问题。

谢谢

【问题讨论】:

    标签: c# multithreading events locking deadlock


    【解决方案1】:

    我不记得曾经需要从 lock 语句中引发事件。但不难想象事情会变得糟糕。

    当您引发事件时,您会将执行推迟到可能不是您自己的代码。如果您正在编写一些库或框架,例如将被其他人使用,则尤其如此。在事件处理程序内部,您完全无法控制发生的事情。事件处理程序可以启动一个全新的线程并等待该线程完成(即Join())然后返回。如果那个新线程调用了某个函数,该函数锁定了与您的lock 相同的变量,宾果游戏。死锁。

    但除此之外,最佳做法是尽量减少在lock 中花费的时间,以减少锁定的“阻塞点”方面。如果您在lock 内提出事件,则所有赌注均无效。

    【讨论】:

    • System.IO.Ports.SerialPort 类使用起来相当简单——因为我以前从未见过它,所以我已经启动并运行得非常快。由于 'DiscardNull' 属性,我丢失了一些字节,并且花了一段时间才意识到即使有一些数据在那里等待,也可能不会调用 'DataReceived' 事件。
    • 很好的答案 - 为什么我总是避免在锁块内调用事件处理程序或委托方法;调用者无法控制的代码。
    【解决方案2】:

    问题不在于事件处理程序可能会尝试调用您已经拥有的锁,问题在于事件处理程序可能会尝试获取其他锁(可能会阻塞并设置死锁),或者事件处理程序可能会启动一些长时间运行的任务,例如数据库查询(在完成之前让其他线程无法访问您的锁)。一般规则是您应该尽可能短地持有锁。

    将线程与您无法控制的事件处理程序混合使用肯定会给您带来麻烦。我目前遇到了一些麻烦,因为我从串行端口的接收线程中引发了一个事件。一些事件处理程序代码决定阻塞并等待,直到从串行端口接收到另一条消息。这将是一个漫长的等待,因为你只是阻止了一个唯一的接收线程!我什至不会对任何人生气,因为我编写了两段代码(相隔一年,所以我有时间忘记细节)。

    【讨论】:

    • @Don Kirkby:完全独立的问题。您提到了“串行端口”。你在 C# 中做这个?有什么好的网站、参考资料、书籍等可以看吗?在接下来的几个月里,我需要解决这个问题。任何帮助,将不胜感激。很抱歉劫持了这个帖子。
    • @Matt:这并不复杂。只需编写一个字符串或字节数组,然后读取 DataReceived 事件的响应或寄存器。我认为您真的不需要手册:msdn.microsoft.com/en-us/library/…
    【解决方案3】:

    是的,从持有的锁中调用事件不会被认为是好的设计。主要是因为锁定部分有一个过渡,如果这会引发错误或出现问题,则不一定会释放锁定。

    一般来说,您希望最大限度地减少您处于锁定状态的时间,以防止持有锁定时间过长(导致延迟),并减少在持有锁定时代码可能会失败的可能性。

    【讨论】:

    • 如果在lock 中调用事件处理程序并引发异常,则锁 被释放。 lock 关键字基本上是包裹在 try-finally 块中的 System.Threading.Monitor.Enter/Exit 的简写符号。也就是说,从 finally 块中调用了 Exit() 方法,因此锁被释放。
    • 其实未必如此。如果异常被传播给拥有锁的调用者是的,它将被释放。如果异常发生在另一个线程上并使调用者处于死锁状态(即等待返回),那么它不会。
    猜你喜欢
    • 2013-04-02
    • 1970-01-01
    • 1970-01-01
    • 2020-01-12
    • 2010-10-16
    • 1970-01-01
    • 2012-07-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多