事件处理程序执行顺序

时间:2009-10-29 17:54:52

标签: c# asynchronous event-handling

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

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

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

11 个答案:

答案 0 :(得分:115)

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

答案 1 :(得分:48)

  

委托的调用列表是   一组有序的代表,其中   列表的每个元素都会调用   正好调用的方法之一   代表。调用列表可以   包含重复的方法。期间   调用,委托调用方法   按它们出现的顺序排列   调用列表

从这里: Delegate Class

答案 2 :(得分:9)

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

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

答案 3 :(得分:8)

它们按照注册顺序运行。 RetrieveDataCompletedMulticast Delegates。我正在通过反射器来尝试验证,看起来像是在幕后使用数组来跟踪所有内容。

答案 4 :(得分:8)

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

编辑并且 - 除非这只是出于好奇 - 您需要知道的事实表明严重的设计问题。

答案 5 :(得分:2)

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

答案 6 :(得分:2)

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

答案 7 :(得分: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'参数进行任何错误处理。

答案 8 :(得分:0)

  

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

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

答案 9 :(得分:0)

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

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

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

答案 10 :(得分:0)

一个简单的例子,表明订单可以与添加的订单不同。

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