等待和事件处理程序
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(...);
}
}
这与示例有两处不同:
- 事件处理程序委托在事件触发后取消注册。否则,只要
Something
存在,委托就会保留在内存中。
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(...);
}
}
}
在这种情况下,只需要一个事件处理程序。每次发生事件时,属性 都会被推送到 Channel
。 Channel.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,这会打开另一个蠕虫罐头。
是否允许将通常的事件处理程序从 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(...);
}
}
这与示例有两处不同:
- 事件处理程序委托在事件触发后取消注册。否则,只要
Something
存在,委托就会保留在内存中。 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(...);
}
}
}
在这种情况下,只需要一个事件处理程序。每次发生事件时,属性 都会被推送到 Channel
。 Channel.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,这会打开另一个蠕虫罐头。