简体   繁体   English

事件处理程序执行顺序

[英]Order of event handler execution

If I set up multiple event handlers, like so:如果我设置多个事件处理程序,如下所示:

_webservice.RetrieveDataCompleted += ProcessData1;
_webservice.RetrieveDataCompleted += ProcessData2;

what order are the handlers run when the event RetrieveDataCompleted is fired?触发事件RetrieveDataCompleted时,处理程序的运行顺序是什么? Are they run in the same thread and sequentially in the order that are registered?它们是否在同一个线程中按注册的顺序运行?

Currently, they are executed in the order they are registered.目前,它们按照注册的顺序执行。 However, this is an implementation detail, and I would not rely on this behavior staying the same in future versions, since it is not required by specifications.然而,这是一个实现细节,我不会依赖这种行为在未来的版本中保持不变,因为规范不需要它。

The invocation list of a delegate is an ordered set of delegates in which each element of the list invokes exactly one of the methods invoked by the delegate.委托的调用列表是一组有序的委托,其中列表的每个元素恰好调用委托所调用的方法之一。 An invocation list can contain duplicate methods.调用列表可以包含重复的方法。 During an invocation, a delegate invokes methods in the order in which they appear in the invocation list .在调用期间,委托按照方法在调用列表中出现的顺序调用方法

From here: Delegate Class从这里开始: 代表班

You can change ordering by detaching all handlers, and then re-attaching in desired order.您可以通过分离所有处理程序来更改顺序,然后按所需顺序重新连接。

public event EventHandler event1;

public void ChangeHandlersOrdering()
{
    if (event1 != null)
    {
        List<EventHandler> invocationList = event1.GetInvocationList()
                                                  .OfType<EventHandler>()
                                                  .ToList();

        foreach (var handler in invocationList)
        {
            event1 -= handler;
        }

        //Change ordering now, for example in reverese order as follows
        for (int i = invocationList.Count - 1; i >= 0; i--)
        {
            event1 += invocationList[i];
        }
    }
}

The order is arbitrary.顺序是任意的。 You cannot rely on the handlers being executed in any particular order from one invocation to the next.您不能依赖从一个调用到下一个调用以任何特定顺序执行的处理程序。

Edit: And also - unless this is just out of curiosity - the fact that you need to know is indicative of a serious design problem.编辑:而且-除非这只是出于好奇-您需要知道的事实表明存在严重的设计问题。

They are run in the order in which they are registered.它们按照注册的顺序运行。 RetrieveDataCompleted is a Multicast Delegates . RetrieveDataCompleted是一个Multicast Delegates I am looking through reflector to try and verify, and it looks like an array is used behind the scenes to keep track of everything.我正在通过反射器尝试验证,看起来像在幕后使用了一个数组来跟踪所有内容。

If someone need to do this in the context of a System.Windows.Forms.Form, here is an example inverting the order of Shown event.如果有人需要在 System.Windows.Forms.Form 的上下文中执行此操作,这里是一个反转 Shown 事件顺序的示例。

using System;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;

namespace ConsoleApplication {
    class Program {
        static void Main() {
            Form form;

            form = createForm();
            form.ShowDialog();

            form = createForm();
            invertShownOrder(form);
            form.ShowDialog();
        }

        static Form createForm() {
            var form = new Form();
            form.Shown += (sender, args) => { Console.WriteLine("form_Shown1"); };
            form.Shown += (sender, args) => { Console.WriteLine("form_Shown2"); };
            return form;
        }

        static void invertShownOrder(Form form) {
            var events = typeof(Form)
                .GetProperty("Events", BindingFlags.Instance | BindingFlags.NonPublic)
                .GetValue(form, null) as EventHandlerList;

            var shownEventKey = typeof(Form)
                .GetField("EVENT_SHOWN", BindingFlags.NonPublic | BindingFlags.Static)
                .GetValue(form);

            var shownEventHandler = events[shownEventKey] as EventHandler;

            if (shownEventHandler != null) {
                var invocationList = shownEventHandler
                    .GetInvocationList()
                    .OfType<EventHandler>()
                    .ToList();

                foreach (var handler in invocationList) {
                    events.RemoveHandler(shownEventKey, handler);
                }

                for (int i = invocationList.Count - 1; i >= 0; i--) {
                    events.AddHandler(shownEventKey, invocationList[i]);
                }
            }
        }
    }
}

A MulticastDelegate has a linked list of delegates, called an invocation list, consisting of one or more elements. MulticastDelegate 有一个委托的链接列表,称为调用列表,由一个或多个元素组成。 When a multicast delegate is invoked, the delegates in the invocation list are called synchronously in the order in which they appear.调用多播委托时,调用列表中的委托按照它们出现的顺序被同步调用。 If an error occurs during execution of the list then an exception is thrown.如果在执行列表期间发生错误,则会引发异常。

During an invocation, methods are invoked in the order in which they appear in the invocation list.在调用期间,方法按照它们在调用列表中出现的顺序被调用。

But nobody says that invocation list maintain delegates in the same order as they are added.但是没有人说调用列表以与添加委托相同的顺序维护委托。 Thus invocation order is not guaranteed.因此不能保证调用顺序。

This is a function that will place the new event handler function wherever you want in the multidelegate invocation list.这是一个函数,它将新的事件处理函数放置在多委托调用列表中的任何位置。

    private void addDelegateAt(ref YourDelegate initial, YourDelegate newHandler, int position)
    {
        Delegate[] subscribers = initial.GetInvocationList();
        Delegate[] newSubscriptions = new Delegate[subscribers.Length + 1];

        for (int i = 0; i < newSubscriptions.Length; i++)
        {
            if (i < position)
                newSubscriptions[i] = subscribers[i];
            else if (i==position)
                newSubscriptions[i] = (YourDelegate)newHandler;
            else if (i > position)
                newSubscriptions[i] = subscribers[i-1];
        }

        initial = (YourDelegate)Delegate.Combine(newSubscriptions);
    }

Then you can always remove the function with a '-=' wherever in your code convenient.然后,您可以随时在代码中方便的地方使用“-=”删除该函数。

PS - I am not doing any error handling for the 'position' parameter. PS - 我没有对“位置”参数进行任何错误处理。

I had a similar problem.我有一个类似的问题。 In my case it was fixed very easily.就我而言,它很容易修复。 I'd never seen a delegate that didn't use the += operator.我从未见过不使用 += 运算符的代表。 My problem was fixed by having one delegate always added at the end, all the others are always added at the beginning.我的问题是通过在最后添加一个代表来解决的,所有其他的总是在开头添加。 The OP's example would be something like: OP 的示例类似于:

    _webservice.RetrieveDataCompleted = _webservice.RetrieveDataCompleted + ProcessData1;
    _webservice.RetrieveDataCompleted = ProcessData2 + _webservice.RetrieveDataCompleted;

In the first case ProcessData1 will be called last.在第一种情况下,ProcessData1 将被最后调用。 In the 2nd case ProcessData2 will be called first.在第二种情况下,将首先调用 ProcessData2。

A minimalistic example to show that the order can differ from the order it was added.一个简单的示例,显示订单可以不同于添加的订单。

public class TestClass
{
    Delegate handlers;

    public event EventHandler Event
    {
        add { handlers = Delegate.Combine(value, handlers ); } // <-- note the order
        remove { handlers = Delegate.Remove(handlers, value); }
    }

    public void RaiseEvents()
    {
        handlers?.DynamicInvoke(this, EventArgs.Empty);
    }
}

One way to manage the order of execution of event handlers in a sequential way is to register only the first handler to the event and invoke the second handler within the first handler routine and in the second handler routine invoke the third one and so on.以顺序方式管理事件处理程序的执行顺序的一种方法是仅将第一个处理程序注册到事件并在第一个处理程序例程中调用第二个处理程序,并在第二个处理程序例程中调用第三个处理程序,依此类推。

Here is a sample code:这是一个示例代码:

public partial class Form1 : Form
{
    EventHandler handler1;
    EventHandler handler2; 
    public Form1()
    {
        InitializeComponent();

        handler1 = ProcessData1;
        handler2 = ProcessData2;
        
        button1.Click += handler1;
    }

    private void ProcessData1(object sender, EventArgs e)
    {
        MessageBox.Show("In handler 1");
        handler2.Invoke(sender, e);
    }

    private void ProcessData2(object sender, EventArgs e)
    {
        MessageBox.Show("In handler 2");
    }
}

The other answers have established that it's very difficult to guarantee execution order.其他答案已经确定很难保证执行顺序。 It's probably true that the events are executed in the order registered, but it might be difficult in your program to guarantee that order.事件按照注册的顺序执行可能是真的,但是在您的程序中可能很难保证该顺序。 For example, I have an application where events are registered by static class constructors, and apparently C# doesn't call those constructors until the class gets utilized the first time, and who knows what order that will be in.例如,我有一个应用程序,其中事件由静态类构造函数注册,显然 C# 不会调用这些构造函数,直到第一次使用该类,谁知道它的顺序是什么。

So, many of the solutions proposed are somewhat complicated methods of changing the order, including Naser Asadi's interesting one of re-registering events.因此,提出的许多解决方案都是更改顺序的有些复杂的方法,包括 Naser Asadi 有趣的重新注册事件之一。 After much thought, I came up with a much simpler approach.经过深思熟虑,我想出了一个更简单的方法。 I decided to NOT ALTER the registration order, but rather alter what happens in each event handler.我决定不改变注册顺序,而是改变每个事件处理程序中发生的事情。

In my application, what I'm trying to do is customize a string, but multiple event handlers might customize it differently, and I don't want to rely on potluck.在我的应用程序中,我想做的是自定义一个字符串,但是多个事件处理程序可能会以不同的方式自定义它,我不想依赖便餐。 Instead, each handler has a different "PRIORITY".相反,每个处理程序都有不同的“优先级”。 Each time the string is updated, the priority is updated also.每次更新字符串时,优先级也会更新。 Other handlers can't touch it unless they have a higher priority.其他处理程序不能触摸它,除非它们具有更高的优先级。 It's a brilliantly simple solution;这是一个非常简单的解决方案; you merely need a way to establish a priority for each handler.您只需要一种方法来为每个处理程序建立优先级。

Below is an example of one of the handlers.下面是其中一个处理程序的示例。 Note that I could create a similar class and handler for vanilla ice cream also, with a different priority, and if both chocolate and vanilla are registered for the same ice cream shop, the one with higher priority will be the one that decides what the ice cream flavor is.请注意,我也可以为香草冰淇淋创建一个类似的类和处理程序,但具有不同的优先级,如果巧克力和香草都在同一个冰淇淋店注册,则优先级较高的那个将决定冰的种类奶油味是。 Note that it DOES NOT MATTER if vanilla or chocolate registers their handler first!请注意,香草或巧克力是否首先注册其处理程序并不重要! Only the priority matters, which YOU can control.只有优先级很重要,您可以控制。

public delegate void CustomizeStringEventHandler(Object sender, CustomizeStringEventArgs e);

public class CustomizeStringEventArgs : EventArgs {
  string _description;
  int _priority;
}

//-----------------------

public class ExampleChocolateIceCreamFlavorClass {
  
  static ExampleChocolateIceCreamFlavorClass() {
    DowntownIceCreamShop.LookupDescription += new CustomizeStringEventHandler(CustomizeString);
    CountryIceCreamShop.LookupDescription += new CustomizeStringEventHandler(CustomizeString);
  }
  
  static void CustomizeString(object sender, CustomizeStringEventArgs e) {
    string desc = "Chocolate"
    int priority = 50; //priority for chocolate
    if ((desc != null) && ((priority > e.Priority) || (e.Description == null))) {
      e.Description = desc;
      e.Priority = priority;
    }
  }
  
}

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

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