如何异步调用事件?

Ano*_*mer 2 c# events multithreading asynchronous

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

我的目标是让他们可以选择使用异步/等待、多线程、后台工作者等,如果他们选择,但仍然能够在事件调用完成之前完成执行。这是一个伪代码示例(我意识到这不能编译,但希望意图很明确):

我的代码:

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);
}
Run Code Online (Sandbox Code Playgroud)

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

// 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

有没有标准的方法来实现这一点?我正在查看委托的 BeginInvoke 和 EndInvoke 方法,希望它们会有所帮助,但我并没有真正想出什么。

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

小智 5

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

在这种情况下,我个人的偏好是使用自定义委托类型返回Task. 同步返回的处理程序可以简单地 return 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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

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