代码改进:此模式的更好替代方案

本文关键字:更好 方案 模式 代码 | 更新日期: 2023-09-27 18:07:04

在类似的问题中:
这种模式叫什么?软锁吗?

我问的是下面代码清单中模式的名称。

  public class MyClass
  {
    public event EventHandler MyEvent;
    private bool IsHandlingEvent = false;
    public MyClass()
    {
      MyEvent += new EventHandler(MyClass_MyEvent);
    }
    void MyClass_MyEvent(object sender, EventArgs e)
    {
      if (IsHandlingEvent) { return; }
      IsHandlingEvent = true;
      {
        // Code goes here that handles the event, possibly invoking 'MyEvent' again.
        // IsHandlingEvent flag is used to avoid redundant processing.  What is this
        // technique, or pattern called.
        // ...
      }
      IsHandlingEvent = false;
    }
  }

似乎大部分的谈话都围绕着为什么我们应该或不应该这样做,所以我认为这个问题提供了一个更好的论坛来解决问题和解决所有的问题。处理这件事的更好/适当的方法是什么?

代码改进:此模式的更好替代方案

这种模式存在一系列问题。如果您只想调用处理程序一次,您可以这样做:

 protected static object _lockObj = new object();
 protected static bool _isHandled = false;    
 void MyClass_MyEvent(object sender, EventArgs e)
 {
     if(_isHandled)
       return;
     lock(_lockObj)
     {
         if(_isHandled)
            return;
         _isHandled = true;
         MyOtherPossiblyRecursiveMethod(); // Actually does all your work
         _isHandled = false;
     }
 }
 void MyOtherPossiblyRecursiveMethod()
 {
 }

这样,只有一个线程能够访问实际的工作方法。

我将使用如下格式:

using( var sl = new SoftLock() )
{
   sl.Execute(()=>{....});
}

执行将引发内部布尔值以防止重新进入。在dispose中,该标志将被重置。如果标志为false, Execute将调用lambda。这是为了确保即使发生异常(导致处理程序从未执行)标志也会变为false,并且可能更好看一些。当然,像原始代码那样,这不是线程安全的,但这是因为我们讨论的是防止同一个线程的双重执行。

原始代码是一种足够的(并且非常轻量级的)方法来防止单线程应用程序中的递归。因此,如果在您的事件处理函数期间,您可以进入可能再次触发事件的代码,您将不会进入无限递归。

但是由于潜在的竞争条件,代码不足以防止多线程访问。如果您需要确保只有一个线程可以运行此事件,那么您应该使用更强的锁定机制,如互斥锁或信号量。

以下内容适用于单线程和多线程场景,并且是异常安全的…此外,如果需要,它可以修改为允许一定级别的可重入(例如3级)…

public class MyClass
{
public event EventHandler MyEvent;
private int IsHandlingEvent = 0;
public MyClass()
{
  MyEvent += new EventHandler(MyClass_MyEvent);
}
void MyClass_MyEvent(object sender, EventArgs e)
{
 // this allows for nesting if needed by comparing for example < 3 or similar
 if (Interlocked.Increment (ref IsHandlingEvent) == 1 )
 {
  try {
      }
  finally {};  
 } 
 Interlocked.Decrement (ref IsHandlingEvent);
}
}