异步等待和事件处理程序

问题描述 投票:0回答:3

允许将常用的事件处理程序从

void
转换为基于
Task
,并且
await
如下所示?

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

更新: 或者这个,我知道应该禁止使用

async void
,因为没有捕获异常,但对于事件处理程序的情况可能没问题,因为事件处理程序不会返回。

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

private async void IsButtonVisible_PropertyChanged(object sender,PropertyChangedEventArgs e)
{
   if (IsSomthingEnabled)
   {
       await = SomeService.ExecuteAsync(...);
   }
}
c# asynchronous async-await eventhandler
3个回答
10
投票

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

Something.PropertyChanged += IsButtonVisible_PropertyChanged;  
... 

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

这允许在事件处理程序内等待异步操作,而不会阻塞 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(...);
    }

}

这与示例有两个地方不同:

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

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

等待事件流

在 C# 8 中引入

IAsyncEnumerable
之前,不可能轻松地实现 await 多个事件。使用
IAsyncEnumerable<T>
Channel,可以创建一个发送通知流的方法:

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

}

在这种情况下,只需要一个事件处理程序。每次发生事件时,指定的属性都会被推送到

Channel
Channel.Reader.ReadAllAsync()
用于返回可用于异步循环的
IAsyncEnumerable<string>
。循环将继续运行,直到
CancellationToken
发出信号,在这种情况下,编写器将进入
Completed
状态,并且
IAsyncEnumerable<T>
将终止。


4
投票

引用微软的文章Async/Await - 异步编程的最佳实践,特别是来自避免 async void 部分:

返回空值的异步方法有一个特定的目的:使异步事件处理程序成为可能。 [...] 事件处理程序自然返回 void,因此异步方法返回 void,以便您可以拥有异步事件处理程序。

基于此,您的第三种方法是正确的:

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

您的第一种方法(

+= async (o, args) => await
)在技术上是等效的,但不推荐它,因为它是惯用的,可能会给未来的维护者带来困惑。

您的第二种方法(

_ = SomeService.ExecuteAsync(
)以“即发即忘”的方式启动异步操作,这并不是一个好主意,因为您的应用程序完全失去了对该任务的跟踪。它还消除了异步和等待,这打开了另一罐蠕虫。


1
投票

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

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

由于事件没有返回,所以没有什么可等待的,所以等待它们是没有意义的

但是,如果您需要事件的响应,则可以使用 EventsArgs 类来提供响应,例如

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 所指出的,这是一个概念性示例,而不是一个工作示例

© www.soinside.com 2019 - 2024. All rights reserved.