简体   繁体   English

有关触发和处理.NET事件的最佳实践

[英]Best practice regarding firing and handling of .NET events

It is obvious that firing events inside of a lock (ie critical section) is prone to deadlocks due to the possibility that the event handler may block on some asynchronous operation that also needs to acquire the same lock. 显然,由于事件处理程序可能会阻塞某些也需要获取同一锁的异步操作,因此触发锁(即关键部分)内的事件很容易导致死锁。 Now, design-wise there are two possible solutions that come to my mind: 现在,在设计方面,我想到了两种可能的解决方案:

  1. If it is needed to fire an event inside a lock, then always fire the event asynchronously. 如果需要在锁内触发事件,则始终以异步方式触发事件。 This can be performed by using ThreadPool, for example, if the firing order of the events does not matter. 例如,如果事件的触发顺序无关紧要,则可以使用ThreadPool执行此操作。 If the order of the events must be preserved, then a single event firing thread can be used to fire the events in order, but asynchronously. 如果必须保留事件的顺序,则可以使用单个事件触发线程按顺序(但异步)触发事件。

  2. The class/library that fires the events does not need to take any necessary precautions to prevent the deadlock and just fire the event inside the lock. 触发事件的类/库不需要采取任何必要的预防措施来防止死锁,而只需在锁内触发事件即可。 In this case it is the event handler's responsibility to process the event asynchronously if it performs locking (or any other blocking operation) inside the event handler. 在这种情况下,如果事件处理程序在事件处理程序内执行锁定(或其他任何阻塞操作),则事件处理程序将负责异步处理该事件。 If the event handler does not conform to this rule, then it should suffer the consequences in case a deadlock occurs. 如果事件处理程序不符合此规则,则在发生死锁的情况下应承担后果。

I honestly think that the second option is better in terms of separation of concerns principle as the event-firing code should not guess what the event handler may or may not do. 老实说,就关注点分离原则而言,第二种方法更好,因为事件触发代码不应猜测事件处理程序可能会或可能不会做什么。

However, practically, I am inclined to take the first route since the second option seems to converge to the point that every event handler must now run all event-handling code asynchronously, since most of the time it is not clear whether some series of calls performs a blocking operation or not. 但是,实际上,我倾向于采用第一种方法,因为第二种方法似乎收敛到每个事件处理程序现在都必须异步运行所有事件处理代码的地步,因为在大多数情况下,尚不清楚是否进行了一系列调用是否执行阻止操作。 For complex event-handlers, tracing all possible paths (and, moreover, keeping track of it as the code evolves) is definitely not an easy task. 对于复杂的事件处理程序,跟踪所有可能的路径(而且,随着代码的发展而对其进行跟踪)绝对不是一件容易的事。 Therefore, solving the problem in one place (where the event is fired) seems to be preferable. 因此,在一个地方(引发事件的地方)解决问题似乎是可取的。

I am interested in seeing if there are other alternative solutions that I may have overlooked and what possible advantages/disadvantages and pitfalls can be attributed to each possible solution. 我很想知道是否还有其他替代解决方案可能被我忽略了,并且每种可能的解决方案都可以归因于哪些可能的优点/缺点和陷阱。

Is there a best practice for this kind of situation? 对于这种情况是否有最佳实践?

There is a third option: Delay raising the event until the lock is released. 第三种选择:延迟引发事件,直到释放锁为止。 Normally, locks are taken for a short time. 通常情况下,锁会被锁定一小段时间。 It is usually possible to delay raising the event till after lock (but on the same thread). 通常可以将引发事件的时间推迟到锁定之后(但在同一线程上)。

The BCL almost never calls user code under a lock. BCL几乎永远不会在锁定状态下调用用户代码。 This is an explicit design principle of theirs. 这是他们的明确设计原则。 For example ConcurrentDictionary.AddOrUpdate does not call the factory while under a lock. 例如, ConcurrentDictionary.AddOrUpdate在处于锁定状态时不会调用工厂。 This counter-intuitive behavior causes many Stack Overflow questions because it can lead to multiple factory invocations for the same key. 这种违反直觉的行为会引起很多堆栈溢出问题,因为它可能导致同一键的多个工厂调用。

I honestly think that the second option is better in terms of separation of concerns principle as the event-firing code should not guess what the event handler may or may not do. 老实说,就关注点分离原则而言,第二种方法更好,因为事件触发代码不应猜测事件处理程序可能会或可能不会做什么。

I don't think the first option violates separation of concerns. 我认为第一种选择不违反关注点分离。 Extract an AsyncEventNotifier class and have your event-generating object delegate to it, something like (obviously not complete): 提取一个AsyncEventNotifier类,并让您的事件生成对象委托给它,类似(显然不完整):

class AsyncEventNotifier
{
    private List<EventListener> _listeners;

    public void AddEventListener(EventListener listener) { _listeners.add(listener); }
    public void NotifyListeners(EventArgs args)
    {
        // spawn a new thread to call the listener methods
    }
    ....
}

class EventGeneratingClass
{
    private AsyncEventHandler _asyncEventHandler;

    public void AddEventListener(EventListener listener) { _asyncEventHandler.AddEventListener(listener); }

    private void FireSomeEvent()
    {
        var eventArgs = new EventArgs();
        ...
        _asyncEventhandler.NotifyListeners(eventArgs);
    }
}

Now your original class isn't responsible for anything it wasn't responsible for before. 现在,您原来的班级不再为以前不负责的任何事情负责。 It knows how to add listeners to itself and it knows how to tell its listeners that an event has occurred. 它知道如何向其自身添加侦听器,并且知道如何告诉其侦听器已发生事件。 The new class knows the implementation details of "tell its listeners that an event has occurred". 新类知道“告诉侦听器事件已发生”的实现细节。 Listener order is also preserved if it's important. 如果重要的话,侦听器的顺序也会保留下来。 It probably shouldn't be, though. 不过,可能不应该如此。 If listenerB can't handle an event until listenerA has already handled it then listenerB is probably more interested in an event generated by listenerA than by the object it was listening to. 如果在listenerA已经处理完事件之前,listenerB无法处理事件,则listenerB可能对listenerA生成的事件更感兴趣,而不是对它正在侦听的对象感兴趣。 Either that or you should have another class whose responsibility is to know the order that an event needs to be processed in. 要么,要么您应该拥有另一个类,其职责是知道处理事件的顺序。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM