延迟事件处理,直到事件被触发为止
在C#中,延迟处理所有已知事件的最佳方式是什么,直到一个实体被完全修改为止? 说,例如,一个实体 - myEntity所 - 具有属性的ID,名称和描述...延迟事件处理,直到事件被触发为止
public class MyEntity
{
public Int32 ID { get; set; }
public String Name { get; set; }
public String Description { get; set; }
}
当修改每个这些属性的,一个事件被触发为每个修改。
有时,ID是修改的唯一属性,有时会修改所有属性。我希望修改事件的注册侦听器等待,直到修改了“批处理”中修改的所有属性。
完成此操作的最佳方法是什么?
在我的脑海中,类似于UnitOfWork-pattern的东西,可以在调用堆栈顶层的方法调用中包装using语句,但不知道如何实现这样的事情......
编辑: 作为一个澄清......听众分散在整个应用程序,并在其他线程执行。另一个actor(例如)设置它必须调用MyEntity.Name属性来设置值的名称。
由于设计原因,Name属性的修改可能触发其他属性的更改,因此需要侦听器知道属性的修改已完成。
只有执行修改的代码才能知道其批次更改是否完成。
我对我的类似课程所做的工作是提供SuspendNotifications()
和ResumeNotifications()
方法,这些方法以明显的方式调用(即在进行一系列更改前调用暂停,完成时调用恢复)。
它们在内部维护一个计数器,该计数器在SuspendNotifications()中递增,并由ResumeNotifications()递减,如果递减结果为零,则会发出通知。我这样做是因为有时候我会修改一些属性,然后调用另一个修改更多的方法,并且本身会调用暂停/恢复。
(如果简历被称为太多次,我抛出的异常。)
如果多个属性被更改,最后通知不名要更改的属性(因为有不止一个)。我想你可以累积一个已更改的属性列表,并将其作为通知的一部分发布,但这听起来不太有用。
另请注意,线程安全性可能会或可能不会成为您的问题。您可能需要使用锁定和/或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";
}
}
}
}
+1我的首选方法。 – 2013-03-13 13:51:44
这看起来有点像我对使用语句的初步想法。我想我可以将事件添加到内部队列,然后在通知恢复时处理队列。 如果是这样 - 我需要队列中的某些东西让听众知道批次何时完成... – ForestC 2013-03-13 14:04:54
这个模式的一个很好的MSDN示例是使用它的[SuspendLayout](http://msdn.microsoft.com/zh-cn/library/system.windows.forms.control.suspendlayout.aspx)和[ResumeLayout]( http://msdn.microsoft.com/en-us/library/system.windows.forms.control.resumelayout.aspx) – 2013-03-13 14:31:24
我认为这将是艰难的,因为事件是异步触发的,但是由执行线程同步处理。一种可能性是使用AutoResetEvent
或ManualResetEvent
并使用WaitOne
-方法来等待Set
发布它。
您可能需要将它与Mutex
结合使用。但是如果你只在一个线程上工作,这将不起作用。
该应用程序是多线程的。也许最好的选择是“收集”一个父事件(子事件是儿童)中的所有事件? – ForestC 2013-03-13 14:07:57
@Forest我不太了解你的程序的结构。如果你能够结合它,它可能会工作。 – 2013-03-13 14:11:03
假设所有的事件都使用相同的签名:
- 在myEntity所实例初始化一个委托例如
eventQueue
,和一个int值,例如,“queueRequiredLength” - 每个属性的setter增加了增加了他们的事件队列如果不是已经存在,
eventQueue += newEvent;
,而不是仅仅发射事件。 - 每个属性的setter然后检查队列的长度并触发委托(即所有排队的事件)
if(length == queueRequiredLength) {eventQueue();}
(关我的头顶,我不知道如何检查的方法数“排队“,但最坏的情况下,你也可以保留一个计数器,每增加一个计数器就增加一个计数器)。
我可以建议
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();
}
}
}
很大程度上取决于其他要求,例如听众对单个财产的变化采取行动还是他们总是处理实体,而不管修改的数量和位置如何? – 2013-03-13 13:34:06
为什么不使用标志?如果标志1和2被设置,但3不是...不要做任何事情。当事件触发并设置了标记3时,THEN会处理您要执行的任何代码。 – 2013-03-13 13:36:25
OT: 我意识到我最初的想法与“使用”语句来自log4net实现,其中调用log4net.NDC.Push()以将上下文消息推送到当前线程的上下文中... – ForestC 2013-03-13 15:02:57