等待和事件处理程序

Mse*_*Ali 2 c# asynchronous async-await eventhandler

允许将通常的事件处理程序从 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;
}
Run Code Online (Sandbox Code Playgroud)

或者像这样做?

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

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

更新: 或者这个,我知道 use Task void 应该被禁止,因为它没有捕获异常,但对于事件处理程序的情况可能没问题,因为事件处理程序不返回。

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

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

Pan*_*vos 9

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

Something.PropertyChanged += IsButtonVisible_PropertyChanged;  
... 

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

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

等待单个事件

如果您希望其他代码等待事件完成,则需要 TaskCompletionSource。任务和基于事件的异步模式 (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(...);
    }

}
Run Code Online (Sandbox Code Playgroud)

这与示例有两个不同之处:

  1. 事件触发后,事件处理程序委托将被取消注册。否则,该代表将像Something以前一样留在记忆中。
  2. TaskCreationOptions.RunContinuationsAsynchronously确保任何延续都将在单独的线程上运行。默认情况下是在设置结果的同一线程上运行它们

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

等待事件流

在 C# 8 中引入IAsyncEnumerable之前,不可能轻松地处理await多个事件。使用Channel,可以创建一个发送通知流的方法:IAsyncEnumerable<T>

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

}
Run Code Online (Sandbox Code Playgroud)

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