如何异步调用事件?

时间:2017-07-28 17:13:16

标签: c# multithreading events asynchronous

我正在构建一个将由其他开发人员使用的控件,并且在一些地方我有开发人员可以订阅的事件来执行一些可能很长的操作。

我的目标是让他们选择使用async / await,multithreading,backgroundworkers等。如果他们选择,但仍然能够在事件调用完成之前完成执行。这是一个伪代码示例(我意识到这不会编译,但希望意图很清楚):

我的代码:

public event MyEventHandler MyEvent;
private void InvokeMyEvent()
{
    var args = new MyEventArgs();

    // Keep the UI responsive until this returns
    await MyEvent?.Invoke(this, args);

    // Then show the result
    MessageBox.Show(args.Result);
}

开发者/订阅者的潜在代码:

// Option 1: The operation is very quick,
// so the dev doesn't mind doing it synchronously
private void myForm_MyEvent(object sender, MyEventArgs e)
{
    // Short operation, I'll just do it right here.
    string result = DoQuickOperation();
    e.Result = result;
}

// Option 2, The operation is long,
// so the dev wants to do it on another thread and keep the UI responsive.
private void myForm_MyEvent(object sender, MyEventArgs e)
{
    myForm.ShowProgressPanel();

    // Long operation, I want to multithread this!
    Task.Run(() =>
    {
        string result = DoVeryLongOperation();
        e.Result = result;
    })
    .ContinueWith((task) =>
    {
        myForm.HideProgressPanel();
    }
}

有没有标准的方法来实现这一目标?我正在查看委托人的BeginInvoke和EndInvoke方法,希望他们能提供帮助,但我并没有真正想出任何东西。

任何帮助或建议将不胜感激!谢谢!

2 个答案:

答案 0 :(得分:3)

您可能会发现我的blog post on async events很有用。

没有严格的约定,但由于WinRT / Win10 API处理此问题的方式存在松散的约定:延迟。我有一个DeferralManager type可以帮到这里:

class MyEventArgs : IDeferralSource
{
  private IDeferralSource _deferralSource;
  public MyEventArgs(IDeferralSource deferralSource) { _deferralSource = deferralSource; }
  IDisposable GetDeferral() => _deferralSource.GetDeferral();

  ... // Other properties
}

public event MyEventHandler MyEvent;
private async Task InvokeMyEventAsync()
{
  var deferralManager = new DeferralManager();
  var args = new MyEventArgs(deferralManager.DeferralSource);

  MyEvent?.Invoke(this, args);

  await deferralManager.WaitForDeferralsAsync();

  MessageBox.Show(args.Result);
}

这种方法具有良好的属性,即同步事件处理程序与正常情况完全相同,同时允许异步事件处理程序。但它并不理想;特别是,没有任何强制异步事件处理程序来获取延迟 - 如果没有,那么它将无法按预期工作。

但是,我必须提醒您,首先使用此类设计的事件是可疑的。在.NET中,事件是Observer设计模式的适当实现。但是你的代码没有使用Observer;它使用模板方法设计模式。您遇到的设计问题是因为您尝试使用事件来实现模板方法设计模式。你可以强迫它(以几种不同的方式),但它不是理想的。

答案 1 :(得分:2)

没有标准的方法可以做到这一点。

当我遇到这种情况时,我的个人偏好是使用返回Task的自定义委托类型。同步返回的处理程序只需返回Task.CompletedTask,它们不需要为此创建新任务。

public delegate Task MyAsyncEventHandler(object sender, MyEventArgs e);
public event MyAsyncEventHandler MyEvent;
private async Task InvokeMyEvent()
{
    var args = new MyEventArgs();

    // Keep the UI responsive until this returns
    var myEvent = MyEvent;
    if (myEvent != null)
        await Task.WhenAll(Array.ConvertAll(
          myEvent.GetInvocationList(),
          e => ((MyAsyncEventHandler)e).Invoke(this, args)));

    // Then show the result
    MessageBox.Show(args.Result);
}

请注意在局部变量中捕获myEvent以避免线程问题,并使用GetInvocationList()来确保等待所有任务。

请注意,在此实现中,如果您有多个事件处理程序,则它们都会一次调度,因此可以并行执行。处理程序需要确保它们不以线程不安全的方式访问args

根据您的使用情况,顺序执行处理程序(循环中await)可能更合适。