【问题标题】:Delay event handling until events have been fired延迟事件处理,直到事件被触发
【发布时间】:2013-03-13 13:20:48
【问题描述】:

在 C# 中,延迟处理所有已知事件直到实体被完全修改的最佳方法是什么? 比如说,一个实体 - MyEntity - 具有属性 ID、名称和描述...

   public class MyEntity
   {
       public Int32 ID { get; set; }
       public String Name { get; set; }
       public String Description { get; set; }
   }

当修改每个属性时,每次修改都会触发一个事件。

有时,ID 是唯一被修改的属性,有时所有属性都被修改。我希望修改事件的注册监听器等到“批处理”中正在修改的所有属性都被修改。

最好的方法是什么?

在我的脑海中,类似于 UnitOfWork 模式的东西,可以在调用堆栈的顶层围绕方法调用包装 using 语句,但不知道如何实现这样的事情......

编辑: 澄清一下...侦听器分布在整个应用程序中,并在其他线程中执行。另一个参与者设置 - 例如 - 它必须调用 MyEntity.Name 属性来设置值的名称。

由于设计上,Name属性的修改会触发其他属性的变化,因此需要监听器知道属性的修改已经完成。

【问题讨论】:

  • 很大程度上取决于其他要求,例如无论修改的数量和位置如何,侦听器是对单个属性更改采取行动还是始终处理实体?
  • 为什么不使用标志?如果设置了标志 1 和 2,但未设置标志 3……则不要做任何事情。当事件触发并设置标志 3 时,然后处理您要执行的任何代码。
  • OT:我意识到我最初对“使用”语句的想法源自 log4net 实现,其中调用 log4net.NDC.Push() 将上下文消息推送到当前线程的上下文...

标签: c# events event-handling


【解决方案1】:

只有执行修改的代码才能知道它的这批更改何时完成。

我对类似类所做的是提供SuspendNotifications()ResumeNotifications() 方法,它们以显而易见的方式调用(即在进行大量更改之前调用suspend,完成后调用resume)。

它们在内部维护一个计数器,该计数器在 SuspendNotifications() 中递增并在 ResumeNotifications() 中递减,如果递减结果为零,则发出通知。我这样做是因为有时我会修改一些属性,然后调用另一个修改了一些属性的方法,而该方法本身会调用挂起/恢复。

(如果resume调用次数过多,我抛出了异常。)

如果更改了多个属性,则最终通知不会命名正在更改的属性(因为有多个)。我想您可以累积更改属性的列表并将其作为通知的一部分发出,但这听起来不是很有用。

另外请注意,线程安全对您来说可能是也可能不是问题。您可能需要使用锁定和/或Interlocked.Increment() 等。

另一件事是,如果出现异常,您当然最终需要 try/catch 来暂停/恢复调用。您可以通过编写实现 IDisposable 并在其 Dispose 中调用 resume 的包装类来避免这种情况。

代码可能如下所示:

public void DoStuff()
{
    try
    {
        _entity.SuspendNotifications();
        setProperties();
    }

    finally
    {
        _entity.ResumeNotifications();
    }
}

private setProperties()
{
    _entity.ID = 123454;
    _entity.Name = "Name";
    _entity.Description = "Desc";
}

[编辑]

如果你要引入一个接口,比如ISuspendableNotifications,你可以编写一个IDisposable 包装类来简化事情。

下面的例子说明了这个概念;使用NotificationSuspender 简化(实际上删除了)try/catch 逻辑。

请注意,class Entity 当然实际上并没有实现挂起/恢复或提供任何错误处理;这留给读者作为众所周知的练习。 :)

using System;

namespace Demo
{
    public interface ISuspendableNotifications
    {
        void SuspendNotifications();
        void ResumeNotifications();
    }

    public sealed class NotificationSuspender: IDisposable
    {
        public NotificationSuspender(ISuspendableNotifications suspendableNotifications)
        {
            _suspendableNotifications = suspendableNotifications;
            _suspendableNotifications.SuspendNotifications();
        }

        public void Dispose()
        {
            _suspendableNotifications.ResumeNotifications();
        }

        private readonly ISuspendableNotifications _suspendableNotifications;
    }

    public sealed class Entity: ISuspendableNotifications
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }

        public void SuspendNotifications() {}
        public void ResumeNotifications() {}
    }

    public static class Program
    {
        public static void Main(string[] args)
        {
            Entity entity = new Entity();

            using (new NotificationSuspender(entity))
            {
                entity.Id = 123454;
                entity.Name = "Name";
                entity.Description = "Desc";
            }
        }
    }
}

【讨论】:

  • 这看起来有点像我最初对 using 语句的想法。我想我可以将事件添加到内部队列,然后在恢复通知时处理队列。如果是这样 - 我需要队列中的一些东西让听众知道批处理何时完成......
  • 这个模式的一个很好的 MSDN 示例来自 Control,它是 SuspendLayoutResumeLayout
  • 这是我想我会选择的解决方案。使用连接到 Resume 和 Suspend 方法的计数器,可以知道何时到达父 using 语句(因此,所有修改都已完成)并且事件可以(几乎)一次发送或打包或聚合到一个有孩子的活动。
【解决方案2】:

我可以建议

public class MyEntity
{
    private const int FieldsCount = 3;

    private Int32 id;
    private String name;
    private String description;

    private HashSet<string> dirty = new HashSet<string>();

    public Int32 ID
    {
        get { return id; }
        set
        {
            id = value;
            dirty.Add("id");
            GoListeners();
        }
    }

    //...

    private void GoListeners()
    {
        if (dirty.Count == FieldsCount)
        {
            //...
            dirty.Clear();
        }
    }

}

【讨论】:

    【解决方案3】:

    我认为这会很困难,因为事件是异步触发的,但由执行线程同步处理。一种可能性是使用AutoResetEventManualResetEvent 并使用WaitOne-方法等待Set 释放它。
    您可能需要将它与Mutex 结合使用。但如果你只在一个线程上工作,这将不起作用。

    请参阅here 了解ManualResetEventhere 了解AutoResetEvent

    【讨论】:

    • 应用程序是多线程的。也许最好的选择是在单个父事件(子事件是子事件)中“收集”所有事件?
    • @Forest 我不太了解您的程序的结构。如果你能把它结合起来,它可能会起作用。
    【解决方案4】:

    假设您的所有事件都使用相同的签名:

    1. 在 MyEntity 实例化时初始化一个委托,例如 eventQueue,以及一个 int 值,例如“queueRequiredLength”
    2. 每个属性设置器都将其事件添加到队列中(如果尚未存在),eventQueue += newEvent; 而不是仅仅触发事件。
    3. 然后每个属性设置器检查队列的长度并触发委托(即所有排队的事件)if(length == queueRequiredLength) {eventQueue();}

    (我不知道如何检查委托中“排队”的方法数量,但最坏的情况是你也可以只保留一个计数器,并在每次添加到队列时增加它)。

    【讨论】:

      猜你喜欢
      • 2019-01-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-07-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多