事件处理程序执行的顺序


93

如果我设置了多个事件处理程序,如下所示:

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

事件RetrieveDataCompleted触发时处理程序的运行顺序是什么?它们是否在相同的线程中运行,并按注册的顺序顺序运行?


2
答案将特定于RetrieveDataCompleted事件。如果它具有多播委托的默认后备存储,则为“它们在同一线程中运行,并按注册的顺序顺序运行”。
HappyNomad

Answers:


131

当前,它们按照注册的顺序执行。但是,这是一个实现细节,我不会依赖此行为在将来的版本中保持不变,因为规范没有要求。


5
我不知道为什么要投票?这是完全正确的,并直接回答了这个问题……
Reed Copsey

2
@Rawling:这是用于二进制运算符重载解析的-不是事件处理。在这种情况下,这不是加法运算符。
里德·科普西

2
啊,我知道我要去哪里了:“事件处理程序是委托,对吗?”。我现在知道他们不是。已经给自己写了一个事件,该事件以相反的顺序触发了处理程序,只是向自己证明了这一点:)
Rawling

16
为了明确起见,顺序取决于特定事件的后备存储。事件的默认后备存储(多播委托)被记录为按注册顺序执行。在将来的框架版本中,这不会改变。可能发生变化的是用于特定事件的后备存储。
HappyNomad

6
拒绝投票,因为它实际上在2分上是不正确的。1)当前,它们按照特定事件的实现指示的顺序执行-因为您可以实现自己的事件添加/删除方法。2)当通过多播委托使用默认事件实现时,规范实际上要求该顺序。
索伦Boisen

53

委托的调用列表是一组有序的委托,其中列表的每个元素都精确地调用委托调用的方法之一。调用列表可以包含重复的方法。在调用过程中,委托以它们在调用列表中出现的顺序调用方法

从这里: 代表班


1
很好,但是使用addremove关键字可以将事件不一定实现为多播委托。
HappyNomad

Bob一样,其他答案都提到将其与事件处理程序一起使用是不可靠的……不管对与错,这个答案也可以讨论这一点。
n611x007 2014年

12

您可以通过分离所有处理程序,然后按所需的顺序重新附加来更改顺序。

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];
        }
    }
}

10

顺序是任意的。您不能依赖于从一个调用到下一个调用以任何特定顺序执行的处理程序。

编辑:而且-除非是出于好奇,否则您需要知道的事实表明存在严重的设计问题。


3
顺序取决于特定事件的实现,但不是任意的。但是,除非事件的文档指出了调用顺序,否则我同意依赖它是有风险的。为此,我发表了一个后续问题
HappyNomad

9
在我看来,要让某个事件需要按类别顺序由不同的类来处理,这似乎并不是一个严重的设计问题。如果事件注册以某种方式进行,则导致难以知道顺序或事件难以知道该顺序很重要,那么就会发生问题。
Ignacio Soler Garcia 2015年

8

它们按照注册的顺序运行。RetrieveDataCompleted是一个多播代表。我正在通过反射器进行尝试和验证,它看起来像在幕后使用了一个数组来跟踪所有内容。


其他答案指出,对于事件处理程序,这是“偶然”,“脆弱”,“实现细节”等,即。任何标准或约定都没有要求,它只是发生。那正确吗?无论如何,这个答案也可以参考。
n611x007 2014年

3

如果有人需要在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]);
                }
            }
        }
    }
}

2

MulticastDelegate具有由一个或多个元素组成的委托链接列表,称为调用列表。调用多播委托时,调用列表中的委托按照它们出现的顺序被同步调用。如果在执行列表期间发生错误,则会引发异常。


2

在调用过程中,方法按照它们在调用列表中出现的顺序被调用。

但是没有人说调用列表会按添加委托的顺序维护委托。因此不能保证调用顺序。


1

此函数会将新的事件处理函数放置在多委托调用列表中的任何位置。

    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);
    }

然后,您可以随时在代码中方便的地方使用“-=”删除该函数。

PS-我没有对'position'参数进行任何错误处理。


0

我有一个类似的问题。就我而言,它很容易修复。我从未见过没有使用+ =运算符的委托。我的问题已得到解决,方法是始终在末尾添加一个代表,而所有其他代表总是在开头添加。OP的示例如下所示:

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

在第一种情况下,ProcessData1将被最后调用。在第二种情况下,将首先调用ProcessData2。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.