等待事件处理程序的执行
Awaiting the execution of event handlers
我有一个数据存储库,它为我的应用程序模型提供了一个持久层。所有对磁盘的访问都是异步的,所以我的整个持久层都是用 async/await 编写的。我的数据仓库允许其他模块订阅数据的变化:
public event EventHandler<Journaling.DataChangeEventArgs> DataChanged;
protected void OnDataChanged(Journaling.Action a)
{
if (DataChanged != null)
{
DataChanged(this, new Journaling.DataChangeEventArgs(a));
}
}
当我告诉存储库删除一个被其他对象引用的对象时,它也会删除这些其他对象。
public async Task<bool> DeleteAsync(Models.BaseModel model)
{
await DeleteDependentModelsAsync(model).ConfigureAwait(false);
if (await connection.DeleteAsync(model).ConfigureAwait(false) != 1)
return false;
else
{
var deleteAction = new Journaling.DeleteAction(model);
OnDataChanged(deleteAction);
return true;
}
}
此设置在一定程度上有效,但我在删除引用其他对象的对象时遇到问题。考虑这个例子:
Object X
Object A1: references X
Object A2: references X
Object A3: references X
我有一个记录器模块,用于订阅数据存储库中的更改并将它们输出到文件中。记录器有时需要从存储库中获取额外的数据,以使其输出可读。删除对象X时的日志应该是:
A1 deleted (parent: X, more information contained in X)
A2 deleted (parent: X, more information contained in X)
A3 deleted (parent: X, more information contained in X)
X deleted
问题是 OnDataChange 不等待事件处理程序的执行。因此,在记录器的事件处理程序被调用一次之前,数据存储库会删除 A1-A3,然后删除 X。但是事件处理程序必须获取一些关于 X 的信息,这不可能了,因为数据存储库已经删除了 X。
我不得不等待 OnDataChanged 中事件处理程序的执行。这样我可以确保记录器在下一个对象从存储中删除之前完成其工作。
任何人都可以在正确的方向上提示我如何做到这一点吗?我考虑过使用信号量,但这会破坏数据存储库和记录器之间的松散耦合。
由于您的处理程序是异步的,并且调用这些处理程序的类型需要知道它们何时完成,因此这些处理程序需要 return Task
而不是 void
。
调用此处理程序时,您需要获取调用列表并调用每个单独的方法,而不是一次调用所有方法,因为您需要能够获取所有 return 值。
您还需要将 OnDataChanged
的签名更改为 return a Task
以便调用者能够知道它何时完成。
public event Func<Journaling.Action, Task> DataChanged;
protected Task OnDataChanged(Journaling.Action a)
{
var handlers = DataChanged;
if (handlers == null)
return Task.FromResult(0);
var tasks = DataChanged.GetInvocationList()
.Cast<Func<Journaling.Action, Task>>()
.Select(handler => handler(a));
return Task.WhenAll(tasks);
}
我有一个blog post on the subject of "asynchronous events"。一般来说,我建议使用 "deferrals",这是 Windows Store API.
的概念
例如,使用我的 AsyncEx 库中的 DeferralManager
类型,您可以首先启用您的事件参数类型以支持延迟:
public class DataChangeEventArgs : EventArgs
{
private readonly DeferralManager _deferrals;
public DataChangeEventArgs(DeferralManager deferrals, Journaling.Action a)
{
_deferrals = deferrals;
}
public IDisposable GetDeferral()
{
return deferrals.GetDeferral();
}
}
然后你这样提出事件:
protected Task OnDataChangedAsync(Journaling.Action a)
{
var handler = DataChanged;
if (handler == null)
return Task.FromResult<object>(null); // or TaskConstants.Completed
var deferrals = new DeferralManager();
var args = new Journaling.DataChangeEventArgs(deferrals, a);
handler(args);
return deferrals.SignalAndWaitAsync();
}
如果需要使用 await
:
,那么消费代码可以使用延迟
async void DataChangedHandler(object sender, Journaling.DataChangeEventArgs args)
{
using (args.GetDeferral())
{
// Code can safely await in here.
}
}
我有一个数据存储库,它为我的应用程序模型提供了一个持久层。所有对磁盘的访问都是异步的,所以我的整个持久层都是用 async/await 编写的。我的数据仓库允许其他模块订阅数据的变化:
public event EventHandler<Journaling.DataChangeEventArgs> DataChanged;
protected void OnDataChanged(Journaling.Action a)
{
if (DataChanged != null)
{
DataChanged(this, new Journaling.DataChangeEventArgs(a));
}
}
当我告诉存储库删除一个被其他对象引用的对象时,它也会删除这些其他对象。
public async Task<bool> DeleteAsync(Models.BaseModel model)
{
await DeleteDependentModelsAsync(model).ConfigureAwait(false);
if (await connection.DeleteAsync(model).ConfigureAwait(false) != 1)
return false;
else
{
var deleteAction = new Journaling.DeleteAction(model);
OnDataChanged(deleteAction);
return true;
}
}
此设置在一定程度上有效,但我在删除引用其他对象的对象时遇到问题。考虑这个例子:
Object X
Object A1: references X
Object A2: references X
Object A3: references X
我有一个记录器模块,用于订阅数据存储库中的更改并将它们输出到文件中。记录器有时需要从存储库中获取额外的数据,以使其输出可读。删除对象X时的日志应该是:
A1 deleted (parent: X, more information contained in X)
A2 deleted (parent: X, more information contained in X)
A3 deleted (parent: X, more information contained in X)
X deleted
问题是 OnDataChange 不等待事件处理程序的执行。因此,在记录器的事件处理程序被调用一次之前,数据存储库会删除 A1-A3,然后删除 X。但是事件处理程序必须获取一些关于 X 的信息,这不可能了,因为数据存储库已经删除了 X。
我不得不等待 OnDataChanged 中事件处理程序的执行。这样我可以确保记录器在下一个对象从存储中删除之前完成其工作。
任何人都可以在正确的方向上提示我如何做到这一点吗?我考虑过使用信号量,但这会破坏数据存储库和记录器之间的松散耦合。
由于您的处理程序是异步的,并且调用这些处理程序的类型需要知道它们何时完成,因此这些处理程序需要 return Task
而不是 void
。
调用此处理程序时,您需要获取调用列表并调用每个单独的方法,而不是一次调用所有方法,因为您需要能够获取所有 return 值。
您还需要将 OnDataChanged
的签名更改为 return a Task
以便调用者能够知道它何时完成。
public event Func<Journaling.Action, Task> DataChanged;
protected Task OnDataChanged(Journaling.Action a)
{
var handlers = DataChanged;
if (handlers == null)
return Task.FromResult(0);
var tasks = DataChanged.GetInvocationList()
.Cast<Func<Journaling.Action, Task>>()
.Select(handler => handler(a));
return Task.WhenAll(tasks);
}
我有一个blog post on the subject of "asynchronous events"。一般来说,我建议使用 "deferrals",这是 Windows Store API.
的概念例如,使用我的 AsyncEx 库中的 DeferralManager
类型,您可以首先启用您的事件参数类型以支持延迟:
public class DataChangeEventArgs : EventArgs
{
private readonly DeferralManager _deferrals;
public DataChangeEventArgs(DeferralManager deferrals, Journaling.Action a)
{
_deferrals = deferrals;
}
public IDisposable GetDeferral()
{
return deferrals.GetDeferral();
}
}
然后你这样提出事件:
protected Task OnDataChangedAsync(Journaling.Action a)
{
var handler = DataChanged;
if (handler == null)
return Task.FromResult<object>(null); // or TaskConstants.Completed
var deferrals = new DeferralManager();
var args = new Journaling.DataChangeEventArgs(deferrals, a);
handler(args);
return deferrals.SignalAndWaitAsync();
}
如果需要使用 await
:
async void DataChangedHandler(object sender, Journaling.DataChangeEventArgs args)
{
using (args.GetDeferral())
{
// Code can safely await in here.
}
}