C#多线程处理和事件

目录

本文呈现的新功能是什么

问题所在

问题详细信息

问题#1 添加和删除事件的可能死锁

问题#2 调用空事件的争用条件

问题#3 添加的事件处理程序未立即调用

问题#4 事件处理程序被删除,但仍被调用

问题#5 由于调用与订阅/取消订阅的另一个线程交互的处理程序的事件而导致死锁

问题 #6 可能无法触发最新的委托列表

问题/功能#7 由于代理可能会不按顺序接收事件

事件的基本问题多线程问题示例

通常推荐的解决方案

解决方案摘要

修复通常推荐的解决方案的示例(发布者)

订阅者#1的示例改进

订阅者#2的示例改进(非顺序——真正的多线程)

解决方案摘要

安全事件类

解决方案摘要

真正的多线程事件处理(SafeEventNS类)

安全事件NS类

SafeEventNS解决方案摘要

另一种方式,单线程访问

解决问题#7

总结


首先,让我们从没有完全安全的多线程事件处理开始,因为必须处理一些警告/条件。因此,您计划使用的特定方法对于您需要的用途是否安全,这实际上是事实。

在书中 基本软件开发职业+技术指南

我将展示一个问题需要解决,我将提供一些类来帮助处理某些场景,并提出一些您可以使用的替代选项,但这取决于您的事件是否可以以这种方式使用。最终,这取决于您需要做什么,以及实现/使用以及您希望管理的复杂性。

本文呈现的新功能是什么

我将展示还有另一个问题需要解决,我将提供一些类来帮助处理某些场景,并提出一些您可以使用的替代选项,但这取决于您的事件是否可以以这种方式使用。最终,这取决于您需要做什么,以及实现/使用以及您希望管理的复杂性。

  1. 乱序事件还有一个额外的问题(请参阅问题#7)并演示如何解决它。
  2. 显示有助于解决问题#6的潜在改进(可能没有最新的订阅者列表)
  3. 演示一个类来包装事件以使其更安全
  4. 演示真正的多线程事件和使用它们的类

问题所在

首先,让我们定义尝试解决此问题的多线程问题的类型:

  1. 由于静态事件的锁(此)或锁(类型)而添加和删除事件的可能死锁
  2. 调用空事件的争用条件
  3. 不会立即调用添加(订阅)的事件处理程序
  4. 仍会调用已删除(取消订阅)的事件处理程序。
  5. 尝试订阅/取消订阅的死锁
  6. 可能无法触发最新的代理列表
  7. 问题/功能——事件可能会不按顺序触发给委托

普遍的共识是没有办法解决所有这些问题。也许但也许让我们看看我们可以解决这些问题的方法。

问题详细信息

问题#1 添加和删除事件的可能死锁

据报道,在最新版本的.net 4.5中使用无锁结构时,这应该不是问题,应该可以避免此类问题。如果您需要在早期版本中处理此问题,您可以使用自己的锁定对象实现自己的添加/删除处理程序,以避免出现死锁情况。

问题#2 调用空事件的争用条件

问题是默认情况下为偶数为空,直到您向其添加处理程序。如果一个线程尝试添加,因为另一个线程正在尝试引发事件,或者最后一个处理程序正在尝试取消订阅,则当您尝试调用该事件时,可能会收到null引用异常。

问题#3 添加的事件处理程序未立即调用

在此方案中,一个线程添加一个处理程序,但中间会引发一个事件,并且此处理程序似乎不会立即调用。

因此,要么需要控制事件源,无需太担心错过的事件,要么需要更像消息总线的东西来重播订阅之前已经发生的事件。

问题#4 事件处理程序被删除,但仍被调用

在这种情况下,一个线程删除了一个处理程序,但在发生这种情况时在另一个线程上引发一个事件,即使它被认为已取消订阅,它仍然会被调用。

问题#5 由于调用与订阅/取消订阅的另一个线程交互的处理程序的事件而导致死锁

在这种情况下,它会导致死锁,因为如果事件机制在订阅或取消订阅以及调用事件时保持锁,则可能会死锁。(有关更多详细信息,请参阅此文章 Threadsafe Events - CodeProject)

问题 #6 可能无法触发最新的委托列表

我们将在推荐的解决方案中看到,代码首先尝试获取要触发的委托事件列表的副本。当它这样做时,它可能无法获得最新的列表,因为另一个线程已经更新了列表。这可能不是什么大问题,它类似于问题#3,因为最终结果是添加的事件订阅可能不会在订阅后立即收到通知

问题/功能#7 由于代理可能会不按顺序接收事件

注意:这更像是事件发布者正确控制事件排序的问题。

如果多个线程同时触发事件,则可能会发生这种情况,如果存在预期的序列,则它们可能会超出预期的顺序向订阅者显示。一旦您确定多个线程可以触发事件,即使正确锁定,这也可以随时发生,一个线程会击败另一个线程以触发事件,使其首先出现。与此相关的#7B)略有不同之处在于,委托列表开始将一个事件发送到列表,然后在第一个事件完成触发到完整列表之前将另一个事件发送到该列表。如果您真的想要多线程事件,您可能会认为这是一个真正支持多线程的功能。许多事件确实有一个典型的顺序。例如获得焦点、失去焦点、会话开始、会话结束(在您的事件中使用EventArgs指示,或通过每个事件的单独事件指示)。避免这种情况的最安全方法是始终从一个线程触发事件(如果这很重要)和/或控制决定使用多线程同步触发事件的控制器,以避免它同时获取多个事件。因此,此问题将存在于任何未执行此操作的多线程方案中。由于这些操作需要由发布者控制,因此可能使用此处未介绍的某种类型的状态管理。

(在文章的最后,展示了一个示例,说明如何处理序列很重要的两个事件之间的事件排序)

我们可以排除问题#1,因为如前所述,它不再是真正的问题。问题#3 如前所述,几乎所有实现都会有,除非您控制事件/时间的来源或实现您的事件,如消息总线以发送新订阅所有以前的事件。.NET事件通常不以这种方式使用。

事件的基本问题多线程问题示例

public ThisEventHandler ThisEvent;

protected virtual OnThisEvent (ThisEventArgs args)
{  
      If (ThisEvent!=null)
         ThisEvent (this,args);  // ThisEvent could be null so you could get a null reference exception 
}

问题

#2 空异常竞争条件

X

#3 新订户可能不会立即被调用

X

#4 取消订阅后仍会调用处理程序。

X

#5 可能的死锁

#6 可能无法触发最新的代表列表

#7 问题/功能——事件可能会不按顺序触发给代表

X

通常推荐的解决方案

让我们从推荐的解决方案开始,以了解它处理什么和不处理什么。

public ThisEventHandler ThisEvent;

protected virtual void OnThisEvent (ThisEventArgs args)
{
   ThisEventHandler thisEvent=ThisEvent;  //assign the event to a local variable
   If (thisEvent!=null)
   {
      thisEvent (this,args);
   }
}

解决方案摘要

这解决了问题#2并且没有问题#5

这有问题:#3#4。也就是说,如果您添加新的处理程序,它可能不会立即被调用,如果您删除处理程序,它可能仍会在短时间内被调用。至少更好的是,我们摆脱了一个问题。我们还看到此解决方案引入了一个可能的#6问题,它在本地thisEvent变量中获取处理程序列表的副本,但由于此变量不是易失性的,因此可能无法获取处理程序列表的最新副本。

问题

#2 空异常竞争条件

#3 新订户可能不会立即被调用

X

#4 取消订阅后仍会调用处理程序。

X

#5 可能的死锁

#6 可能无法获取要调用的处理程序的最新副本

X

#7 问题/功能——事件可能会不按顺序触发给代表

X

解决问题#3(添加新处理程序——它不会立即调用)(如果可能)的一种方法是在多个线程可能调用您的事件之前尽早添加处理程序。这避免了#3的问题。

对于取消订阅时的#4,设置一个布尔标志,事件处理程序可以检查它是否被取消订阅。如果是,什么都不做。或者只是在事件处理程序中进行智能处理,以了解何时发生这种情况。问题是没有安全的方法来做到这一点而不锁定。

让我们看看我们是否可以做得更好:

修复通常推荐的解决方案的示例(发布者)

在事件发布者中,如果我们使用此代码:

[ThreadStatic] volatile ThisEventHandler _localEvent = null; //need volatile to ensure we get the latest
public virtual void OnThisEvent (EventArgs args)
{
   _localEvent=ThisEvent; //assign the event to a local variable – notice is volatile class field
   if (_localEvent!=null)
   {
     _localEvent(this,args);
   }
}

在事件发布者中不使用锁时,请注意变量_localEvent您可能在Internet上看到的许多示例中,将事件的副本保存到变量中,如果不是null,则调用它。它们通常不使用类易失性字段。否则,他们可能不会获得最新的副本。使用类字段与局部变量的净效果可能不是那么大的问题,因为当调用一个方法时,局部变量将至少初始化一次通常是预期的。在C#中,局部变量不能是易失性的,因此为了确保读/写定向到内存,可以使用易失性类字段。

订阅者#1的示例改进

在事件订阅者类中,如果我们使用它:

private object _lock = new object();  
private volatile bool _unsubscribed=false;

private void MethodToUnsubscribe()
{
 	lock(_lock)
       {
       _unsubscribed = true;
                
       	ThisEvent -= new ThisEventHandler(ThisEventChanged);
	}
}
public void Subscribe()
{
       lock(_lock)
	{
            _unsubscribed = false;
                
            ThisEvent += new ThisEventHandler(ThisEventChanged);
       }            
}

//Event handler
private void ThisEventChanged(object sender, ThisEventArgs e) 
{
   
	lock(_lock)
{
       	if (!_unsubscribed)
       	{
			//Do work here
       	}
	}
	
}

订阅者#2的示例改进(非顺序——真正的多线程)

在事件订阅者类中,如果我们使用它:

private ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();  
private volatile bool _unsubscribed=false;

private void MethodToUnsubscribe()
{
 	_rwLock.EnterWriteLock();
       _unsubscribed = true;
                
       ThisEvent -= new ThisEventHandler(ThisEventChanged);
       _rwLock.ExitWriteLock();
}
public void Subscribe()
{
            
            _rwLock.EnterWriteLock();
            _unsubscribed = false;
                
            ThisEvent += new ThisEventHandler(ThisEventChanged);
            _rwLock.ExitWriteLock();
            
}

//Event handler – due to read lock could get called out of sequence by multiple threads
private void ThisEventChanged(object sender, ThisEventArgs e) 
{
   
_rwLock.EnterReadLock();
	try
	{
       	if (!_unsubscribed)
       	{
			//Do work here
       	}
	}
	finally
{
       	_rwLock.ExitReadLock();         
      }
}

解决方案摘要

不幸的是,这给事件的订阅者带来了一些负担。

重要说明:此示例代码还允许多个线程同时触发调用,这可能是不需要的。如果不需要,只需使用常规锁而不是读取器/写入器锁,否则您的事件可能会不按顺序出现。

如果实现发布服务器和订阅者代码:

问题

#2 空异常竞争条件

#3 新订户可能不会立即被调用

#4 取消订阅后仍会调用处理程序。

#5 可能的死锁

X

#6 可能无法获取要调用的处理程序的最新副本

#7 问题/功能——事件可能会不按顺序触发给代表

X

如果您的事件可以通过这种方式处理,那么上述一些问题要么得到解决,要么得到解决。如果您的代码无法处理此方法的限制,还有其他实现,但它们会遇到列出的一个或多个问题。因此,这将归结为您的代码要求和您希望如何处理多线程事件的实现复杂性。(或者只使用来自单个线程的事件)。

如果只实现事件发布者代码而不实现订阅者代码:

问题

#2 空异常竞争条件

#3 新订户可能不会立即被调用

X

#4 取消订阅后仍会调用处理程序。

X

#5 可能的死锁

X

#6 可能无法获取要调用的处理程序的最新副本

#7 问题/功能——事件可能会不按顺序触发给代表

X

这两种解决方案都解决了标准建议解决方案中显示的问题#6,方法是在发布者代码中使用易失性字段来确保复制事件处理程序的最新副本。

上面的方法演示如何在使用事件的代码中而不是在发布事件的代码中处理此问题。

让我们看一个示例,该示例总结了逻辑以更安全地处理事件:

安全事件类

让我们看一下包装类实现,它将所有这些放在一起,以便事件的实现者可以处理大多数问题,而不会给listener/susbcriber带来负担。下面的代码是可用于事件发布者的实现。此实现支持触发的多线程非顺序事件。

/// <summary>
/// SafeHandlerInfo - used for storing handler info, plus contains a flag
/// indicating if subscribed or not and the reader writer lock
/// for when the subscription is read or updated.
/// </summary>
/// <typeparam name="TArgs">Event args</typeparam>
internal class SafeHandlerInfo< TArgs> where TArgs : EventArgs
{
    public EventHandler<TArgs> Handler;
    public bool Subscribed = true;
    //public object LockObj = new object();

    public ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();

    public SafeHandlerInfo(EventHandler<TArgs> handler)
    {
        Handler = handler;
    }

}

/// <summary>
/// <summary>
/// SafeEvent class provides safety for unsubscribing from events so even with
/// multiple threads after unsubscribing it will not get called.
/// Also makes sure that a null exception won't happen due to the removing of
/// events.  Only one event is fired at a time, so single threaded through this event.
/// </summary>
/// <typeparam name="TArgs">The type of the event args</typeparam>
public class SafeEvent<TArgs>
    where TArgs : EventArgs
{

    private object _lock = new object();

    private event EventHandler<TArgs> _event;



    public event EventHandler<TArgs> Event
    {
        add
        {
            Subscribe(value);
        }

        remove
        {
            Unsubscribe(value);
        }

    }



    /// <summary>
    /// Used to fire this event from within the class using the SafeEvent
    ///
    /// </summary>
    /// <param name="args">The event args</param>
    public virtual void FireEvent(object sender,TArgs args)
    {

        lock (_lock)
        {
            EventHandler<TArgs> localEvents = _event;
            if (localEvents != null)
                localEvents(sender, args);

        }

    }


    /// <summary>
    /// Unsubscribe - internally used to unsubscribe a handler from the event
    /// </summary>
    /// <param name="unsubscribeHandler">The handler being unsubscribed</param>
    protected void Unsubscribe(EventHandler<TArgs> unsubscribeHandler)
    {

        lock (_lock)
        {
            _event -= unsubscribeHandler;
        }

    }

    /// <summary>
    /// Subscribe - Called to subscribe the handler
    /// </summary>
    /// <param name="eventHandler">The handler to subscribe</param>
    protected void Subscribe(EventHandler<TArgs> eventHandler)
    {
        lock (_lock)
        {
            _event += eventHandler;
        }
    }
}

解决方案摘要

上面的代码从发布端解决了类似的问题。

问题#1——添加和删除订阅者的潜在死锁

由于我们在这里添加了自己的锁,问题#1又回来了

问题

#2 空异常竞争条件

#3 新订户可能不会立即被调用

#4 取消订阅后仍会调用处理程序。

#5 可能的死锁

X

#6 可能无法获取要调用的处理程序的最新副本

#7 问题/功能——事件可能会不按顺序触发给代表

X

在某些情况下,此代码容易受到死锁的影响

潜在死锁——如果他们取消订阅当前正在触发的事件的现有处理程序,则可能会出现死锁。因此,要遵循的最佳规则是不要在调用事件的过程中尝试取消订阅,并避免在该事件调用的任何下游函数中这样做。

希望这应该是非常可控的,因为作为事件的订阅者,您应该知道何时要取消订阅。仅当被触发的单独线程导致正在触发的当前事件的取消订阅时,才会发生这种情况。所以机会相当有限。

接下来,让我们举一个如何使用SafeEvent类的示例:

/// <summary>
/// TestEventUser
/// Test the SafeEvent class for implementing an event using SafeEvent
/// </summary>
public class TestEventUse
{
    //declare our private event
    private SafeEvent<EventArgs> _myEvent = new SafeEvent<EventArgs>();

    /// <summary>
    /// MyEvent - proxy the add/remove calls to the private event
    /// </summary>
    public event EventHandler<EventArgs> MyEvent
    {
        add
        {
            _myEvent.Event += value;
        }

        remove
        {
            _myEvent.Event -= value;

        }

    }

    /// <summary>
    /// OnMyEvent - standard example idiom of how to fire an event
    /// </summary>
    /// <param name="args"></param>
    protected void OnMyEvent(EventArgs args)
    {

        _myEvent.FireEvent(this,args); //call our private event to fire it
    }

    /// <summary>
    /// FireEvent - This we provided on our test class as a quick way to fire the event from another class.
    /// </summary>
    public void FireEvent()
    {
        OnMyEvent(new EventArgs());
    }

}

真正的多线程事件处理(SafeEventNS类)

您将看到的大多数事件处理实现要么提到了许多多线程问题,要么只允许单个线程一次从单个线程触发单个事件。

SafeEventsNS将允许多个线程同时触发事件,订阅者可以同时获得多线程调用。这意味着通知可能不会按顺序发送。这不是大多数事件的正常处理方式,大多数事件处理程序可能关心传入的事件顺序。但是,如果您不这样做,并且您需要一种相对安全的方式来同时将事件从多个线程触发到多个订阅者,那么此类可能会很有用。

安全事件NS

/// <summary>
/// SafeHandlerInfo - used for storing handler info, plus contains a flag
/// indicating if subscribed or not and the reader writer lock
/// for when the subscription is read or updated.
/// </summary>
/// <typeparam name="TArgs">Event args</typeparam>
internal class SafeHandlerInfo< TArgs> where TArgs : EventArgs
{
    public EventHandler<TArgs> Handler;
    public bool Subscribed = true;
    //public object LockObj = new object();

    public ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();

    public SafeHandlerInfo(EventHandler<TArgs> handler)
    {
        Handler = handler;
    }

}

/// <summary>
/// SafeEvent class provides safety for unsubscribing from events
/// so even with multiple threads after unsubscribing it will not get called.
/// Also makes sure that a null exception won't happen due to the removing of events
/// </summary>
/// <typeparam name="TArgs">The type of the event args</typeparam>
public class SafeEventNS<TArgs>
    where TArgs : EventArgs

{

    Dictionary<EventHandler<TArgs>,
        SafeHandlerInfo<TArgs>> _handlers = new Dictionary<EventHandler<TArgs>,
        SafeHandlerInfo<TArgs>>();
    private ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();

    public event EventHandler<TArgs> Event {
        add
        {
            Subscribe(value);
        }

        remove
        {
            Unsubscribe(value);
        }

    }

    /// <summary>
    /// Used to fire this event from within the class using the SafeEvent
    /// This reads a copy of the list and calls all of the subscribers in it that are still subscribed.
    /// Anything that was unsubscribed gets removed at the end of the event call.  It was handler here since
    /// The copy of the list might be held by multiple threads unsubscribe flags a handler unsubscribed and removes it.
    /// that way if it is still in the list it will not be called
    ///
    /// </summary>
    /// <param name="args">The event args</param>
    public virtual void FireEvent(object sender,TArgs args)
    {
        _rwLock.EnterReadLock();
        List<SafeHandlerInfo<TArgs>> localHandlerInfos = _handlers.Values.ToList();
        _rwLock.ExitReadLock();

        foreach (SafeHandlerInfo<TArgs> info in localHandlerInfos)
        {
            info.Lock.EnterReadLock();
            try
            {
                if (info.Subscribed)
                {
                    EventHandler<TArgs> handler = info.Handler;
                    try
                    {
                        handler(sender, args);
                    }
                    catch { };
                }

            }
            finally
            {
                info.Lock.ExitReadLock();
            }
        }

    }


    /// <summary>
    /// Unsubscribe - internally used to unsubscribe a handler from the event
    /// </summary>
    /// <param name="unsubscribeHandler">The handler being unsubscribed</param>
    protected void Unsubscribe(EventHandler<TArgs> unsubscribeHandler)
    {

        _rwLock.EnterWriteLock();
        try
        {

            SafeHandlerInfo<TArgs> handler = null;

            if (_handlers.TryGetValue(unsubscribeHandler, out handler))
            {
                handler.Lock.EnterWriteLock();
                try
                {

                    handler.Subscribed = false;

                    _handlers.Remove(handler.Handler);

                }
                finally
                {
                    handler.Lock.ExitWriteLock();
                }
            }
        }
        catch (Exception e)
        {
            throw e;
        }
        finally
        {
            _rwLock.ExitWriteLock();
        }

    }

    /// <summary>
    /// Subscribe - Called to subscribe the handler
    /// </summary>
    /// <param name="eventHandler">The handler to subscribe</param>
    protected void Subscribe(EventHandler<TArgs> eventHandler)
    {

        _rwLock.EnterWriteLock();
        try
        {

            SafeHandlerInfo<TArgs> handlerInfo = null;

            if (!_handlers.TryGetValue(eventHandler, out handlerInfo))
            {
                handlerInfo = new SafeHandlerInfo<TArgs>(eventHandler);


                handlerInfo.Lock.EnterWriteLock();
                try
                {

                    _handlers.Add(eventHandler, handlerInfo);
                }
                finally
                {
                    handlerInfo.Lock.ExitWriteLock();
                }
            }
            else
            {
                handlerInfo.Lock.EnterWriteLock();
                try
                {

                    handlerInfo.Subscribed = true;
                }
                finally
                {
                    handlerInfo.Lock.ExitWriteLock();
                }

            }
        }
        catch (Exception e)
        {
            throw e;
        }
        finally
        {
            _rwLock.ExitWriteLock();
        }

    }

}

让我们展示一个如何使用它的示例,它与前面的SafeEvent类相同。

/// <summary>
    /// TestEventUser
    /// Test the SafeEvent class for implementing an event using SafeEvent
    /// </summary>
    public class TestSafeEventNS
    {
        //declare our private event 
        private SafeEventNS<EventArgs> _myEvent = new SafeEventNS<EventArgs>();

        /// <summary>
        /// MyEvent - proxy the add/remove calls to the private event
        /// </summary>
        public event EventHandler<EventArgs> MyEvent  
        {
            add
            {
                _myEvent.Event += value;
            }

            remove
            {
                _myEvent.Event -= value;

            }

        }

        /// <summary>
        /// OnMyEvent - standard example idiom of how to fire an event
        /// </summary>
        /// <param name="args"></param>
        protected void OnMyEvent(EventArgs args)
        {

            _myEvent.FireEvent(this,args); //call our private event to fire it
        }

        /// <summary>
        /// FireEvent - This we provided on our test class as a quick way to fire the event from another class.
        /// </summary>
        public void FireEvent()
        {
            OnMyEvent(new EventArgs());
        }

    }

SafeEventNS解决方案摘要

SafeEventNS类仍然存在死锁的可能性,但如果需要,可以提供真正的多线程事件支持的好处。

问题#1——添加和删除订阅者的潜在死锁

由于我们在这里添加了自己的锁,问题#1又回来了

问题

#2 空异常竞争条件

#3 新订户可能不会立即被调用

#4 取消订阅后仍会调用处理程序。

#5 可能的死锁

X

#6 可能无法获取要调用的处理程序的最新副本

#7 问题/功能——事件可能会不按顺序触发给代表

X

提醒问题#7必须由事件的发布者处理,仅使用多线程同步以正确的顺序发布。

另一种方式,单线程访问

因此,正如代码项目文章和原作者John Skeet中提到的,有一种方法可以解决其中的许多问题,即不要订阅/取消订阅或触发来自多个线程的事件,只使用一个线程。这将避免锁定开销和一些复杂性,并解决所有问题(某种程度上)。它确实增加了线程模型的一些复杂性。同样,如果它解决了#3#4问题,就像将订阅放入队列以在另一个线程上处理一样,这是有争议的,那么您不知道何时最终会被订阅或取消订阅。如果您在订阅或取消订阅时构建了异步通知,这可能是尝试解决此问题的一种方法。

对于其他潜在的实现,请查看前面提到的文章,它解释了实现以及每个实现存在的问题:

Threadsafe Events - CodeProject

解决问题#7

如前所述,此问题是指事件可以不按顺序触发。为了解决这个问题,必须控制事件源,因此,如果存在所需的事件顺序,则可以保护状态机,并在多个线程可能触发状态机时通过同步触发这些事件。

/// <summary>
/// TestEventUseSolveProblemSeven
/// Test the SafeEvent class for implementing an event using
/// SafeEvent – we want StartEvent to be fired before EndEvent
/// This shows how to solve problem #7.  If FireEvent() is that
/// only thing that could be used to fire events then
/// that will guarantee a StartEvent is called before an EndEvent()
/// </summary>
public class TestEventUseSolveProblemSeven
{
    //declare our private event
    private SafeEvent<EventArgs> _startEvent = new SafeEvent<EventArgs>();
    private SafeEvent<EventArgs> _endEvent = new SafeEvent<EventArgs>();
    private object _lock = new object();
    bool _started = false;

    /// <summary>
    /// StartEvent - proxy the add/remove calls to the private event
    /// </summary>
    public event EventHandler<EventArgs> StartEvent
    {
        add
        {
            _startEvent.Event += value;
        }

        remove
        {
            _startEvent.Event -= value;

        }

    }

    public event EventHandler<EventArgs> EndEvent
    {
        add
        {
            _endEvent.Event += value;
        }

        remove
        {
            _endEvent.Event -= value;

        }

    }

    /// <summary>
    /// OnStartEvent - standard example idiom of how to fire an event
    /// </summary>
    /// <param name="args"></param>
    protected void OnStartEvent(EventArgs args)
    {

        _startEvent.FireEvent(this, args); //call our private event to fire it
    }

    /// <summary>
    /// OnEndEvent - standard example idiom of how to fire an event
    /// </summary>
    /// <param name="args"></param>
    protected void OnEndEvent(EventArgs args)
    {

        _endEvent.FireEvent(this, args); //call our private event to fire it
    }

    /// <summary>
    /// FireEvent - This we provided on our test class as a quick way to fire
    /// the event from another class.
    /// </summary>
    public void FireEvent()
    {
        // by using a lock to prevent other events from being fired and managing our
        // state we can guarantee event ordering.
        lock (_lock)
        {
            if (_started)
                OnEndEvent(new EventArgs());
            else
                OnStartEvent(new EventArgs());
            _started = !_started;

        }
    }

}

总结

因此,正如我们所看到的,对于所有多线程问题都没有直接的简单答案。因此,您需要根据您的目的/要求选择最佳答案,并处理可能正确发生的场景以避免事件/多线程问题。

https://www.codeproject.com/Articles/886223/Csharp-Multithreading-and-Events

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值