简体   繁体   English

等待事件处理程序的执行

[英]Awaiting the execution of event handlers

I have a data repository that provides a persistence layer for the models of my application. 我有一个数据存储库,为我的应用程序的模型提供持久层。 All access to the disc is asynchronous so my whole persistence layer is written with async/await. 对光盘的所有访问都是异步的,因此我的整个持久层都是用async / await编写的。 My data repository allows other modules to subscribe to changes in the data: 我的数据存储库允许其他模块订阅数据中的更改:

 public event EventHandler<Journaling.DataChangeEventArgs> DataChanged;   
 protected void OnDataChanged(Journaling.Action a)
 {
      if (DataChanged != null)
      {
           DataChanged(this, new Journaling.DataChangeEventArgs(a));
      }
 }

When I tell the repository to delete an object that is referenced by other objects, it deletes these other objects as well. 当我告诉存储库删除其他对象引用的对象时,它也会删除这些其他对象。

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

This setup works to some extent, but I have a problem when deleting objects that reference other objects. 此设置在某种程度上有效,但在删除引用其他对象的对象时遇到问题。 Consider this example: 考虑这个例子:

Object X 
  Object A1: references X
  Object A2: references X
  Object A3: references X

I have a logger module that subscribes to changes in the data repository and outputs them to a file. 我有一个记录器模块,它订阅数据存储库中的更改并将它们输出到文件中。 The logger sometimes needs to fetch additional data from the repository to make its output human readable. 记录器有时需要从存储库中获取其他数据,以使其输出可读。 The log when deleting Object X should be: 删除对象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

The problem is that OnDataChange doesn't await the execution of the event handlers. 问题是OnDataChange不等待事件处理程序的执行。 Because of this the data repository deletes A1-A3 and then X before the event handler of the logger is even called once. 因此,数据存储库在记录器的事件处理程序甚至被调用一次之前删除A1-A3然后X. But the event handler has to fetch some information about X, which isn't possible any more because the data repository has already deleted X. 但事件处理程序必须获取有关X的一些信息,这是不可能的,因为数据存储库已经删除了X.

I somehow have to await the execution of the event handler in OnDataChanged. 我不得不等待在OnDataChanged中执行事件处理程序。 This way I could make sure that the logger has finished its work before the next object is deleted from storage. 这样我可以确保记录器在从存储中删除下一个对象之前已完成其工作。

Can anyone hint me in the right direction on how to do this? 任何人都可以在正确的方向上暗示我如何做到这一点? I have considered using semaphores but this would break the loose coupling I have between the data repository and the logger. 我考虑过使用信号量,但这会破坏我在数据存储库和记录器之间的松散耦合。

I have a blog post on the subject of "asynchronous events" . 我有一篇关于“异步事件”主题的博客文章 In general, I recommend using "deferrals", which are a concept from the Windows Store API. 通常,我建议使用“deferrals”,这是Windows Store API中的一个概念。

For example, using the DeferralManager type from my AsyncEx library, you can first enable your event arguments type to support deferrals: 例如,使用DeferralManager类型从我AsyncEx库中,可以先使你的事件参数类型,支持延期:

public class DataChangeEventArgs : EventArgs
{
  private readonly DeferralManager _deferrals;

  public DataChangeEventArgs(DeferralManager deferrals, Journaling.Action a)
  {
    _deferrals = deferrals;
  }

  public IDisposable GetDeferral()
  {
    return deferrals.GetDeferral();
  }
}

Then you raise the event as such: 然后你举起这样的事件:

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

Consuming code can then use a deferral if it needs to use await : 如果需要使用await则使用代码可以使用延迟:

async void DataChangedHandler(object sender, Journaling.DataChangeEventArgs args)
{
  using (args.GetDeferral())
  {
    // Code can safely await in here.
  }
}

Since your handlers are asynchronous, and the type invoking those handlers needs to know when they finish, those handlers need to return a Task instead of being void . 由于处理程序是异步的,并且调用这些处理程序的类型需要知道它们何时完成,因此这些处理程序需要返回Task而不是void

When invoking this handler you'll need to get the invocation list and invoke each individual method, rather than invoking all at once, because you need to be able to get all of the return values. 调用此处理程序时,您需要获取调用列表并调用每个单独的方法,而不是一次调用所有方法,因为您需要能够获取所有返回值。

You'll also need to alter the signature of OnDataChanged to return a Task so that the caller will be able to know when it has finished. 您还需要更改OnDataChanged的签名以返回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);

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM