代码之家  ›  专栏  ›  技术社区  ›  Jérémie Bertrand Alex Kumbhani

安全提升事件线程-最佳实践

  •  37
  • Jérémie Bertrand Alex Kumbhani  · 技术社区  · 14 年前

    protected virtual void OnSomethingHappened(EventArgs e) 
    {
        EventHandler handler = SomethingHappened;
        if (handler != null) 
        {
            handler(this, e);
        }
    }
    

    但是这个有什么不同呢?

    protected virtual void OnSomethingHappened(EventArgs e) 
    {
        if (SomethingHappened!= null) 
        {
            SomethingHappened(this, e);
        }
    }
    

    显然第一个是线程安全的,但是为什么和如何呢?

    不需要开始新的线程吗?

    10 回复  |  直到 14 年前
        1
  •  53
  •   Fredrik Mörk    7 年前

    很有可能 SomethingHappened 变成 null 在空检查之后但在调用之前。然而, MulticastDelagate blog post about this 不久前)。

    不过,这是有道理的;如果您使用temp变量方法,您的代码将受到保护 NullReferenceException s、 但事件可能会调用事件侦听器 在他们脱离事件之后 . 这只是一件需要以最优雅的方式处理的事情。

    为了解决这个问题,我有时会使用一种扩展方法:

    public static class EventHandlerExtensions
    {
        public static void SafeInvoke<T>(this EventHandler<T> evt, object sender, T e) where T : EventArgs
        {
            if (evt != null)
            {
                evt(sender, e);
            }
        }
    }
    

    使用该方法,可以调用如下事件:

    protected void OnSomeEvent(EventArgs e)
    {
        SomeEvent.SafeInvoke(this, e);
    }
    
        2
  •  40
  •   Krzysztof Branicki    7 年前

    由于C#6.0,您可以使用一元空条件运算符 ?. 以简单且线程安全的方式检查null并引发事件。

    SomethingHappened?.Invoke(this, args);
    

    它的线程安全,因为它只对左侧求值一次,并将其保存在临时变量中。你可以读更多 here 部分标题为空条件运算符。

    更新: announcement .

        3
  •  14
  •   Jesse C. Slicer    14 年前

    我将此片段作为设置和触发的安全多线程事件访问的参考:

        /// <summary>
        /// Lock for SomeEvent delegate access.
        /// </summary>
        private readonly object someEventLock = new object();
    
        /// <summary>
        /// Delegate variable backing the SomeEvent event.
        /// </summary>
        private EventHandler<EventArgs> someEvent;
    
        /// <summary>
        /// Description for the event.
        /// </summary>
        public event EventHandler<EventArgs> SomeEvent
        {
            add
            {
                lock (this.someEventLock)
                {
                    this.someEvent += value;
                }
            }
    
            remove
            {
                lock (this.someEventLock)
                {
                    this.someEvent -= value;
                }
            }
        }
    
        /// <summary>
        /// Raises the OnSomeEvent event.
        /// </summary>
        public void RaiseEvent()
        {
            this.OnSomeEvent(EventArgs.Empty);
        }
    
        /// <summary>
        /// Raises the SomeEvent event.
        /// </summary>
        /// <param name="e">The event arguments.</param>
        protected virtual void OnSomeEvent(EventArgs e)
        {
            EventHandler<EventArgs> handler;
    
            lock (this.someEventLock)
            {
                handler = this.someEvent;
            }
    
            if (handler != null)
            {
                handler(this, e);
            }
        }
    
        4
  •  13
  •   rpeshkov    11 年前

    对于.NET4.5,最好使用 Volatile.Read 分配临时变量。

    protected virtual void OnSomethingHappened(EventArgs e) 
    {
        EventHandler handler = Volatile.Read(ref SomethingHappened);
        if (handler != null) 
        {
            handler(this, e);
        }
    }
    

    本文对此进行了解释: http://msdn.microsoft.com/en-us/magazine/jj883956.aspx

    主要思想是JIT编译器可以优化代码并删除局部临时变量。所以这个代码:

    protected virtual void OnSomethingHappened(EventArgs e) 
    {
        EventHandler handler = SomethingHappened;
        if (handler != null) 
        {
            handler(this, e);
        }
    }
    

    将编译成:

    protected virtual void OnSomethingHappened(EventArgs e) 
    {
        if (SomethingHappened != null) 
        {
            SomethingHappened(this, e);
        }
    }
    

        5
  •  7
  •   jgauffin    14 年前

    像这样声明事件以获得线程安全:

    public event EventHandler<MyEventArgs> SomethingHappened = delegate{};
    

    像这样调用它:

    protected virtual void OnSomethingHappened(MyEventArgs e)   
    {  
        SomethingHappened(this, e);
    } 
    

    虽然这种方法已经不需要了。。

        6
  •  7
  •   Brian Gideon    14 年前

    这取决于你所说的线程安全。如果你的定义只包括预防 NullReferenceException 第一个例子是 更多 安全。但是,如果使用更严格的定义 必须 如果它们存在则被调用 也不

    • 使用 lock
    • 使用 volatile
    • 使用 Thread.MemoryBarrier .

    尽管有尴尬的范围界定问题,这使你不能做一行初始化我仍然喜欢 方法。

    protected virtual void OnSomethingHappened(EventArgs e)           
    {          
        EventHandler handler;
        lock (this)
        {
          handler = SomethingHappened;
        }
        if (handler != null)           
        {          
            handler(this, e);          
        }          
    }          
    

    值得注意的是,在这种特定情况下,内存障碍问题可能没有实际意义,因为变量的读取不太可能在方法调用之外解除。但是,没有任何保证,特别是当编译器决定内联该方法时。

        7
  •  3
  •   Nicole Calinoiu    14 年前

    实际上,第一个是线程安全的,但是第二个不是,第二个的问题是somethingochapped委托可以在null验证和调用之间更改为null。有关更完整的解释,请参阅 http://blogs.msdn.com/b/ericlippert/archive/2009/04/29/events-and-races.aspx .

        8
  •  1
  •   Curt Nichols    14 年前

    实际上,不,第二个例子不被认为是线程安全的。SomeThingOccessed事件可以在条件中计算为非null,然后在调用时为null。这是一个典型的比赛条件。

        9
  •  1
  •   Community Reversed Engineer    7 年前

    Jesse C. Slicer 的答案是:

    • 在提升期间从任何线程订阅/取消订阅的能力(已删除竞态条件)
    • 类级别上+=和-=的运算符重载
    • 通用调用方定义的委托

      public class ThreadSafeEventDispatcher<T> where T : class
      {
          readonly object _lock = new object();
      
          private class RemovableDelegate
          {
              public readonly T Delegate;
              public bool RemovedDuringRaise;
      
              public RemovableDelegate(T @delegate)
              {
                  Delegate = @delegate;
              }
          };
      
          List<RemovableDelegate> _delegates = new List<RemovableDelegate>();
      
          Int32 _raisers;  // indicate whether the event is being raised
      
          // Raises the Event
          public void Raise(Func<T, bool> raiser)
          {
              try
              {
                  List<RemovableDelegate> raisingDelegates;
                  lock (_lock)
                  {
                      raisingDelegates = new List<RemovableDelegate>(_delegates);
                      _raisers++;
                  }
      
                  foreach (RemovableDelegate d in raisingDelegates)
                  {
                      lock (_lock)
                          if (d.RemovedDuringRaise)
                              continue;
      
                      raiser(d.Delegate);  // Could use return value here to stop.                    
                  }
              }
              finally
              {
                  lock (_lock)
                      _raisers--;
              }
          }
      
          // Override + so that += works like events.
          // Adds are not recognized for any event currently being raised.
          //
          public static ThreadSafeEventDispatcher<T> operator +(ThreadSafeEventDispatcher<T> tsd, T @delegate)
          {
              lock (tsd._lock)
                  if (!tsd._delegates.Any(d => d.Delegate == @delegate))
                      tsd._delegates.Add(new RemovableDelegate(@delegate));
              return tsd;
          }
      
          // Override - so that -= works like events.  
          // Removes are recongized immediately, even for any event current being raised.
          //
          public static ThreadSafeEventDispatcher<T> operator -(ThreadSafeEventDispatcher<T> tsd, T @delegate)
          {
              lock (tsd._lock)
              {
                  int index = tsd._delegates
                      .FindIndex(h => h.Delegate == @delegate);
      
                  if (index >= 0)
                  {
                      if (tsd._raisers > 0)
                          tsd._delegates[index].RemovedDuringRaise = true; // let raiser know its gone
      
                      tsd._delegates.RemoveAt(index); // okay to remove, raiser has a list copy
                  }
              }
      
              return tsd;
          }
      }
      

    用法:

        class SomeClass
        {   
            // Define an event including signature
            public ThreadSafeEventDispatcher<Func<SomeClass, bool>> OnSomeEvent = 
                    new ThreadSafeEventDispatcher<Func<SomeClass, bool>>();
    
            void SomeMethod() 
            {
                OnSomeEvent += HandleEvent; // subscribe
    
                OnSomeEvent.Raise(e => e(this)); // raise
            }
    
            public bool HandleEvent(SomeClass someClass) 
            { 
                return true; 
            }           
        }
    

    代码只是在insert上进行了短暂的测试和编辑。
    预先确认该列表<gt;如果元素太多,则不是一个很好的选择。

        10
  •  0
  •   Martin Brown    14 年前

    为了使这两个都是线程安全的,您假设订阅事件的所有对象也是线程安全的。