允许将常用的事件处理程序从
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(...);
}
}
异步事件处理程序的语法是:
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(...);
}
}
这与示例有两个地方不同:
Something
一样留在记忆中。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>
将终止。
引用微软的文章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(
)以“即发即忘”的方式启动异步操作,这并不是一个好主意,因为您的应用程序完全失去了对该任务的跟踪。它还消除了异步和等待,这打开了另一罐蠕虫。
异步事件处理程序的语法是
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 所指出的,这是一个概念性示例,而不是一个工作示例