延迟事件处理,直到事件被触发

本文关键字:事件 事件处理 延迟 | 更新日期: 2023-09-27 18:03:37

在c#中,延迟处理所有已知事件直到实体被完全修改的最好方法是什么?比如说,一个实体——MyEntity——有属性ID, Name和Description…

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

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

有时,ID是唯一被修改的属性,有时所有属性都被修改。我希望修改事件的注册侦听器等待,直到"批处理"中被修改的所有属性都被修改。

实现这一目标的最佳方法是什么?

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

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

由于设计的原因,Name属性的修改可能会触发其他属性的更改,因此需要侦听器知道属性的修改已经完成。

延迟事件处理,直到事件被触发

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

我对类似的类所做的是提供SuspendNotifications()ResumeNotifications()方法,它们以明显的方式调用(即在进行一堆更改之前调用suspend,完成时调用resume)。

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

(如果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";
            }
        }
    }
}

我可以建议

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

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

ManualResetEvent见这里,AutoResetEvent见这里

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

  1. 在MyEntity实例化时初始化一个委托,例如eventQueue,并初始化一个int值,例如'queueRequiredLength'
  2. 每个属性设置器添加添加他们的事件到队列中,如果还没有出现,eventQueue += newEvent;而不是仅仅触发事件。
  3. 每个属性设置器然后检查队列的长度并触发委托(即所有排队的事件)if(length == queueRequiredLength) {eventQueue();}

(我不确定如何检查委托中"排队"的方法数量,但最坏的情况下,你也可以保留一个计数器,并随着每次加入队列而增加)