.net 记录事件';s调用顺序

.net 记录事件';s调用顺序,.net,event-handling,.net,Event Handling,调用特定事件的处理程序的顺序取决于该特定事件的实现。例如,使用多案例委托的默认备份存储,将按注册顺序调用处理程序。但是类设计者/实现者可能已经使用了add和remove关键字为事件访问器提供了不同的备份存储,因此调用顺序也将不同 在.NET framework基本库本身中是否存在事件文档精确描述其调用顺序的情况?无论是否存在,依赖此类记录的订单(例如,对于我自己实施和记录的事件)是否被视为可接受的做法?为什么?我不知道。这几乎总是以先进先出结束,因为不使用列表不是很有效。MulticastDel

调用特定事件的处理程序的顺序取决于该特定事件的实现。例如,使用多案例委托的默认备份存储,将按注册顺序调用处理程序。但是类设计者/实现者可能已经使用了
add
remove
关键字为事件访问器提供了不同的备份存储,因此调用顺序也将不同


在.NET framework基本库本身中是否存在事件文档精确描述其调用顺序的情况?无论是否存在,依赖此类记录的订单(例如,对于我自己实施和记录的事件)是否被视为可接受的做法?为什么?我不知道。这几乎总是以先进先出结束,因为不使用列表不是很有效。MulticastDelegate和EventHandlerList就是这样工作的


依赖订单是有风险的。在很多情况下,程序员取消订阅事件以防止再次进入问题,在方法退出时再次订阅。由于不可避免的副作用是顺序将改变,他的事件处理程序现在将被称为最后一个。如果这导致程序失败,那么程序员将困惑相当长一段时间。他只是看不到代码更改和错误行为之间的联系,这是一个很难修复的bug。尽管如此,让代码以意想不到的方式交互永远是一个陷阱,只有好的设计才能避免它,只有好的调试器才能诊断它。

在我发布这个问题的那天,我关注的是一个注册顺序明显且稳定的特殊情况。但第二天,我已经注意到这是一个例外,而不是规则

处理程序可以通过任何代码路径添加到任何地方,因此通常不可能按照该顺序执行任何有用的操作。因此,一般规则是忽略注册顺序,并尝试使代码工作,而不管处理程序以何种顺序添加/调用。这有时需要采取预防措施,即使您已经有了工作代码。在我目前关注的案例中,这需要创建一个伴随的
Changing
事件,与
Changed
事件配对,这样必须首先发生的事情将进入
Changing
事件的处理程序

我想您可以记录一个事件的顺序调用顺序,以用于注册明显且稳定的罕见情况。但是,对于依赖于顺序的每个注册,您还需要记录其顺序重要性,在代码发展过程中,您必须记住这一点,否则会出现一些问题。听起来需要做很多工作,所以只要遵守上段提到的一般规则可能会更容易


我可以想出一种可靠控制调用顺序的可行方法。您可以作为处理程序注册的一部分传入优先级。这样,根据顺序的注册顺序,您是而不是。但是您正在控制相对调用顺序。这样的实现是更重的和非标准的,因此在大多数情况下可能不太理想。

< P>我遇到了一个场景,我认为在编写测试时它是完全可以接受的。考虑此助手类,用于异步加载,并在操作完成时通过事件通知:

public class AsyncItemLoader<T>
{
    /// <summary>
    /// Handlers will be invoked in registration order, pinky-swear!
    /// Also, they are invoked on a thread from the ThreadPool.
    /// </summary>
    public EventHandler<T> ItemLoaded;

    public void LoadAsync()
    {
        // load the item asynchronously using Task.ContinueWith to fire 
        // ItemLoaded event to indicate that the item is now available
    }
}

试图通过暂时取消活动订阅来解决重新入场的问题是创造毛茸茸的比赛条件的一个极好的方法:
public class MyAppModel
{
    private readonly AsyncItemLoader<User> userLoader;

    public AsyncItemLoader<User> User { get { return userLoader; } }

    public MyAppModel()
    {
        this.userLoader = new AsyncItemLoader<User>(...);
        this.userLoader += HandleUserLoaded;
    }

    public void StartLoadingUser()
    {
        userLoader.LoadAsync();
    }

    private void HandleUserLoaded(object sender, User user)
    {
        // do something with the user here
    }
}
public class MyAppModelTest
{
    [Test]
    public void StartLoadingUserCausesMyAppModelToDoStuffWithOtherDep()
    {
        var model = new MyAppModel();
        var itemLoaded = new ManualResetEventSlim(initialState: false);
        model.User.ItemLoaded += (s, e) => itemLoaded.Set();

        model.StartLoadingUser();
        itemLoaded.Wait();

        // At this point we are guaranteed that MyAppModel.HandleUserLoaded 
        // has finished execution
        myServiceMock.Received().AmazingServiceCall();
    } 
}