【发布时间】:2010-12-28 06:35:59
【问题描述】:
我通常听说最好在调用事件侦听器之前解锁任何锁以避免死锁。但是,由于lock {} 块在 C# 中由同一线程可重入,是否可以从锁定的块中调用事件,还是需要制作相关状态数据的副本并在锁定块外调用事件?
如果不是,请举例说明何时从 lock {} 块中调用事件会出现问题。
谢谢
【问题讨论】:
标签: c# multithreading events locking deadlock
我通常听说最好在调用事件侦听器之前解锁任何锁以避免死锁。但是,由于lock {} 块在 C# 中由同一线程可重入,是否可以从锁定的块中调用事件,还是需要制作相关状态数据的副本并在锁定块外调用事件?
如果不是,请举例说明何时从 lock {} 块中调用事件会出现问题。
谢谢
【问题讨论】:
标签: c# multithreading events locking deadlock
我不记得曾经需要从 lock 语句中引发事件。但不难想象事情会变得糟糕。
当您引发事件时,您会将执行推迟到可能不是您自己的代码。如果您正在编写一些库或框架,例如将被其他人使用,则尤其如此。在事件处理程序内部,您完全无法控制发生的事情。事件处理程序可以启动一个全新的线程并等待该线程完成(即Join())然后返回。如果那个新线程调用了某个函数,该函数锁定了与您的lock 相同的变量,宾果游戏。死锁。
但除此之外,最佳做法是尽量减少在lock 中花费的时间,以减少锁定的“阻塞点”方面。如果您在lock 内提出事件,则所有赌注均无效。
【讨论】:
问题不在于事件处理程序可能会尝试调用您已经拥有的锁,问题在于事件处理程序可能会尝试获取其他锁(可能会阻塞并设置死锁),或者事件处理程序可能会启动一些长时间运行的任务,例如数据库查询(在完成之前让其他线程无法访问您的锁)。一般规则是您应该尽可能短地持有锁。
将线程与您无法控制的事件处理程序混合使用肯定会给您带来麻烦。我目前遇到了一些麻烦,因为我从串行端口的接收线程中引发了一个事件。一些事件处理程序代码决定阻塞并等待,直到从串行端口接收到另一条消息。这将是一个漫长的等待,因为你只是阻止了一个唯一的接收线程!我什至不会对任何人生气,因为我编写了两段代码(相隔一年,所以我有时间忘记细节)。
【讨论】:
是的,从持有的锁中调用事件不会被认为是好的设计。主要是因为锁定部分有一个过渡,如果这会引发错误或出现问题,则不一定会释放锁定。
一般来说,您希望最大限度地减少您处于锁定状态的时间,以防止持有锁定时间过长(导致延迟),并减少在持有锁定时代码可能会失败的可能性。
【讨论】:
lock 中调用事件处理程序并引发异常,则锁将 被释放。 lock 关键字基本上是包裹在 try-finally 块中的 System.Threading.Monitor.Enter/Exit 的简写符号。也就是说,从 finally 块中调用了 Exit() 方法,因此锁将被释放。