我正在编写一个C#类,该类装饰可以引发事件的对象。装饰器可以实例化一个新的装饰对象,并交换旧的对象,以响应在任何使用线程上发生的异步事件。在初始化新的装饰对象时,我需要将所有事件处理程序从旧的装饰对象添加到新的装饰对象,并继续从两个对象添加/删除处理程序,直到发生交换。是否存在解决此问题的通用解决方案?
这是正在发生的事情的概念性示例:
interface IFoo
{
event Action Barred;
void Bar();
}
class BasicFoo : IFoo
{
public event Action Barred;
public void Bar()
{
Console.WriteLine("Barring");
Barred?.Invoke();
}
}
class DecoratedFoo : IFoo
{
private IFoo _Decorated;
public DecoratedFoo()
{
_Decorated = new BasicFoo();
}
public event Action Barred
{
add => _Decorated.Barred += value;
remove => _Decorated.Barred -= value;
}
public void Bar() => _Decorated.Bar();
public void SwapDecoratedFoo()
{
// Can occur at any time from any thread.
var newFoo = new BasicFoo();
/*
* How to reassign events from _Decorated to newFoo, and in a way
* that's thread safe while _Decorated.Barred may still be adding
* or removing handlers while the swap is occurring?
*/
Interlocked.Exchange(ref _Decorated, newFoo);
}
}
答案 0 :(得分:0)
执行此操作的正常方法是在Decorated包装器中拥有您自己的事件,并在包装的(基本)对象的事件触发时调用该事件。您还需要除Interlocked之外的另一种锁定,因为Interlocked不足以将多个语句组合在一起。您可以使用SemaphoreSlim(1,1)进行操作,如下所示:
interface IFoo
{
event Action Barred;
void Bar();
}
class BasicFoo : IFoo
{
public event Action Barred;
public void Bar() { … }
}
class DecoratedFoo : IFoo
{
public DecoratedFoo(IFoo foo = null) => this.Decorated = foo;
public event Action Barred;
public void Bar()
{
try {
this._lock.Wait();
this._decorated?.Bar();
} finally {
this._lock.Release();
}
}
private SemaphoreSlim _lock = new SemaphoreSlim(1, 1);
public IFoo Decorated {
get {
try {
this._lock.Wait();
return this._decorated;
} finally {
this._lock.Release();
}
}
set {
try {
this._lock.Wait();
if (this._decorated != null) {
this._decorated.Barred -= this.OnDecoratedBarred;
}
this._decorated = value;
if (this._decorated != null) {
this._decorated.Barred += this.OnDecoratedBarred;
}
} finally {
this._lock.Release();
}
}
}
private IFoo _decorated = null;
private void OnDecoratedBarred() => this.Barred?.Invoke();
}
对此有一个评论,设计假定IFoo.Bar()不回调用户代码。如果确实如此,那么当/当用户代码调用DecoratedFoo.Bar()时,死锁的可能性就很高。在这种情况下,应该使设计异步以防止死锁。
使用this.Barred似乎是线程安全的,请参见Maven repo。另外,由于事件处理程序是按顺序调用的,因此事件处理程序不会(至少不会直接)相互干扰的风险很小。