代码改进:有更好的模式替代方案吗?

3
在一个类似的问题中:
这种模式叫什么?软锁定?
我在询问下面代码清单的模式名称。
  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;
    }
  }

似乎大部分讨论都集中在为什么应该或不应该这样做,所以我认为本问题提供了更好的讨论场所,可以解决所有问题。如何更好/正确地处理此事呢?

4个回答

3

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

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

这样一来,只有一个线程可以访问实际的工作方法。


1
如果在同一线程上发生重新引发(即A.R.讨论的情况),您确定锁定将起作用吗? - Felice Pollano
1
你可能还想将_isHandled声明为volatile。或者更好的方法是,避免使用双重检查锁定,仅在lock块内部检查_isHandled - LukeH
@LukeH 如果你在lock块内部检查_isHandled状态,那么有时候调用线程会被冻结,直到MyOtherPossiblyRecursiveMethod()完成并且该块被解锁。最好在这里进行两次检查。 - Artemix
@Artemix:也许是,也许不是。但在这种情况下,如果您不将“_isHandled”标记为易失性,则理论上可能会出现线程读取“_isHandled”的旧版本并且即使应该进入“lock”块时也永远不会进入。 - LukeH

1
我将使用类似以下代码:
using( var sl = new SoftLock() )
{
   sl.Execute(()=>{....});
}

执行会引发内部布尔值以防止重新进入。在处理时,该标志将被重置。仅当标志为假时,Execute才会调用lambda。这是为了确保即使发生异常(导致处理程序从未执行),标志也会变为false,可能更易于查看。当然,这不是线程安全的,就像原始代码一样,但这是因为我们正在讨论如何防止来自同一线程的双重执行。


1

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

但是,由于存在竞争条件的潜在可能性,该代码不足以防止多个线程访问。如果您需要确保只有一个线程可以运行此事件,则应使用更强的锁定机制,例如Mutex或Semaphore。


0

以下代码适用于单线程和多线程场景,并且具有异常安全性... 如果需要,它可以被修改以允许一定程度的可重入性(例如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);
}
}

如果我们在同一个线程上,Interlocked没有任何需要互锁的内容... - Felice Pollano
不确定您看到了什么问题... 这段代码只是旨在与单线程和多线程场景一起使用... 如果 OP 通常只使用单线程,那么这将完全没有问题,并且可以实现他想要的功能... 作为“奖励”,当转移到多线程设计时,他可以保持代码不变... 另一个好处是它是异常安全的... - Yahia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接