等待和事件处理程序

await and event handler

是否允许将通常的事件处理程序从 void 转换为基于任务并像下面这样等待它?

Something.PropertyChanged += async (o, args) => await IsButtonVisible_PropertyChanged(o, args);  
Something.PropertyChanged -= async (o, args) => await IsButtonVisible_PropertyChanged(o, args);  

private Task IsButtonVisible_PropertyChanged(object sender,PropertyChangedEventArgs e)
{
   if (IsSomthingEnabled)
   {
       return SomeService.ExecuteAsync(...);
   }

   return Task.CompletedTask;
}

还是这样?

Something.PropertyChanged += IsButtonVisible_PropertyChanged;  
Something.PropertyChanged -= IsButtonVisible_PropertyChanged;  

private void IsButtonVisible_PropertyChanged(object sender,PropertyChangedEventArgs e)
{
   if (IsSomthingEnabled)
   {
       _ = SomeService.ExecuteAsync(...);
   }
}

更新: 或者这个,我知道使用 Task void 它应该被禁止,因为它没有被捕获,但对于 Eventhandler 来说可能没问题,因为 Eventhandler 没有 return.

Something.PropertyChanged += IsButtonVisible_PropertyChanged;  
Something.PropertyChanged -= IsButtonVisible_PropertyChanged;  

private async void IsButtonVisible_PropertyChanged(object sender,PropertyChangedEventArgs e)
{
   if (IsSomthingEnabled)
   {
       await = SomeService.ExecuteAsync(...);
   }
}

异步事件处理程序的语法是

async void handler(object sender,EventArgs args){}

并且由于事件不会 return 没有什么可等待的,所以等待它们是没有意义的

然而,如果您需要事件的响应,那么您可以使用 EventsArgs class 来提供响应,例如

class FeedbackEventArgs:EventArgs
{
    event EventHandler Completed;
    Complete(){
        this.Completed(this,EventArgs.Empty);
    }
}

那么你就可以把它当作

event EventHandler<FeedbackEventArgs> myFeedbackEvent;

args = new FeedbackEventArgs();
args.Completed += OnCompleted;
this.myFeedbackEvent(this,args)

请注意,如果您的处理程序不是异步的,那么您可以假设您的代码在事件发生时暂停,在这种情况下,您只需从 eventArg 中读取 属性 而不必触发事件

class FeedbackEventArgs:EventArgs
{
    int result{get;set;}
}

event EventHandler<FeedbackEventArgs> myFeedbackEvent;

this.myFeedbackEvent(this,args)
args.result //this will be the result set in the sync handler

如@Panagiotis 所述,这是一个概念性示例,而不是工作示例

异步事件处理程序的语法是:

Something.PropertyChanged += IsButtonVisible_PropertyChanged;  
... 

private async void IsButtonVisible_PropertyChanged(object sender,
                                                   PropertyChangedEventArgs e)
{
   if (IsSomethingEnabled)
   {
       await SomeService.ExecuteAsync(...);
   }
}

这允许在事件处理程序中等待异步操作,而不会阻塞 UI 线程。但是,这不能用于等待其他方法中的事件。

等待单个事件

如果您希望一些其他代码等待事件完成,您需要一个 TaskCompletionSource。 Tasks and the Event-based Asynchronous Pattern (EAP).

中对此进行了解释
public Task<string> OnPropChangeAsync(Something x)
{
     var options=TaskCreationOptions.RunContinuationsAsynchronously;
     var tcs = new TaskCompletionSource<string>(options);
     x.OnPropertyChanged += onChanged;
     return tcs.Task;

     void onChanged(object sender,PropertyChangedEventArgs e)
     {
         tcs.TrySetResult(e.PropertyName);
         x.OnPropertyChanged -= onChanged;
     }
     
}

....

async Task MyAsyncMethod()
{
    var sth=new Something();
    ....
    var propName=await OnPropertyChangeAsync(sth);
   
    if (propName=="Enabled" && IsSomethingEnabled)
    {
        await SomeService.ExecuteAsync(...);
    }

}

这与示例有两处不同:

  1. 事件处理程序委托在事件触发后取消注册。否则,只要 Something 存在,委托就会保留在内存中。
  2. TaskCreationOptions.RunContinuationsAsynchronously 确保任何延续都将 运行 在单独的线程上。默认是 运行 它们在设置结果的同一个线程上

此方法将只等待一个事件。循环调用,每次都会创建一个新的TCS,很浪费

等待事件流

IAsyncEnumerable was introduced in C# 8. With IAsyncEnumerable<T> and Channel 之前不可能轻松 await 多个事件,可以创建一个发送通知流的方法:

public IAsyncEnumerable<string> OnPropChangeAsync(Something x,CancellationToken token)
{
     var channel=Channel.CreateUnbounded<string>();
     //Finish on cancellation
     token.Register(()=>channel.Writer.TryComplete());
     x.OnPropertyChanged += onChanged;
     
     return channel.Reader.ReadAllAsync();

     async void onChanged(object sender,PropertyChangedEventArgs e)
     {
         channel.Writer.SendAsync(e.PropertyName);
     }
     
}

....

async Task MyAsyncMethod(CancellationToken token)
{  
    var sth=new Something();
    ....
    await foreach(var prop in OnPropertyChangeAsync(sth),token)
    {
   
        if (propName=="Enabled" && IsSomethingEnabled)
        {
           await SomeService.ExecuteAsync(...);
        }
    }

}

在这种情况下,只需要一个事件处理程序。每次发生事件时,属性 都会被推送到 ChannelChannel.Reader.ReadAllAsync()用于return一个可以用来异步循环的IAsyncEnumerable<string>。循环将保持 运行ning 直到 CancellationToken 发出信号,在这种情况下,编写器将进入 Completed 状态并且 IAsyncEnumerable<T> 将终止。

引自微软文章Async/Await - Best Practices in Asynchronous Programming, and specifically from the Avoid async void部分:

Void-returning async methods have a specific purpose: to make asynchronous event handlers possible. [...] Event handlers naturally return void, so async methods return void so that you can have an asynchronous event handler.

据此,你的第三种做法是正确的:

private async void IsButtonVisible_PropertyChanged(object sender,
    PropertyChangedEventArgs e)
{
   if (IsSomethingEnabled)
   {
       await SomeService.ExecuteAsync();
   }
}

您的第一种方法 (+= async (o, args) => await) 在技术上是等效的,但不推荐使用,因为它是惯用的并且可能会给未来的维护者造成混淆。

您的第二种方法 (_ = SomeService.ExecuteAsync() 以即发即弃的方式启动异步操作,这很少是一个好主意,因为您的应用程序完全无法跟踪此任务。它还 elides async and await,这会打开另一个蠕虫罐头。