C# 具有有状态事件参数的异步事件处理程序
有时,我们希望事件发布者在触发后的操作取决于事件处理程序设置的某些状态。一个常见的例子是采用C# 具有有状态事件参数的异步事件处理程序,c#,events,asynchronous,C#,Events,Asynchronous,有时,我们希望事件发布者在触发后的操作取决于事件处理程序设置的某些状态。一个常见的例子是采用CancelEventArgs的事件。事件发布者在触发后根据Cancel属性的值执行操作。如果处理程序长时间运行,我们不希望阻止UI线程(用户可以在等待时做其他事情)。但是异步处理程序可能在设置状态之前返回,并且发布服务器在需要时将没有正确的值。我通过在事件参数中提供一个可变任务属性来解决这个问题。处理程序负责设置它的值,发布程序负责在触发后等待它。这似乎很有效 一个反对意见可能是,有状态事件参数可以说是
CancelEventArgs
的事件。事件发布者在触发后根据Cancel属性的值执行操作。如果处理程序长时间运行,我们不希望阻止UI线程(用户可以在等待时做其他事情)。但是异步处理程序可能在设置状态之前返回,并且发布服务器在需要时将没有正确的值。我通过在事件参数中提供一个可变任务属性来解决这个问题。处理程序负责设置它的值,发布程序负责在触发后等待它。这似乎很有效
一个反对意见可能是,有状态事件参数可以说是错误的做法,除非假设只有一个处理程序。您可以使用高阶函数而不是事件,这将通过强制执行一个“处理程序”来处理上述异议
我不确定的一件事是async/await如何处理多个等待器
- 是否有任何关于延续执行顺序的保证李>
- 是否会有一个竞态条件,在该竞态条件下,触发的其余部分可能会在事件处理程序的其余部分之前执行李>
- 我做这件事的方式是否有我没有提到的缺点李>
- 是否有其他一些众所周知或公认的最佳实践来实现我不知道的目标李>
- 关于如何处理比赛情况有什么想法吗李>
class Publisher
{
void RaiseMyEvent()
{
var e = new MyEventArgs();
OnRaiseMyEvent(e);
if (e.Task != null) await e.HandlerTask;
if (e.Cancel)
{
// Do one thing
}
else
{
// Do the other
}
}
}
class Subscriber
{
void MyEventHandler(object sender, CancelEventArgs e)
{
// Notify user to wait on process
e.Task = SomeAsyncMethod();
await e.Task;
e.Cancel = GetOutcome();
// Clear any notification
}
bool GetOutcome() { }
}
实际上,我们可以通过确保在处理程序中继续之前设置触发方法所需的事件args中的任何所需状态值来避免竞争:
class Subscriber
{
void MyEventHandler(object sender, CancelEventArgs e)
{
// Notify user to wait on process
e.Task = Task.Run(() =>
{
//Do stuff
e.Cancel = GetOutcome();
}
await e.Task;
// Clear any notification
}
bool GetOutcome() { }
}
两个continuation都在UI线程上执行,但我们不关心顺序
有时,我们希望事件发布者在触发后的操作取决于事件处理程序设置的某些状态
我将这些事件称为“命令事件”,而不是“通知事件”,并涵盖
经过相当多的经验,我得出结论,命令事件是一种反模式。NET中的事件被设计为通知事件,让它们以不同的方式运行充其量是很尴尬的。使用术语,.NET事件用于实现观察者设计模式,但这些“命令事件”实际上是模板方法设计模式的实现
考虑以下关于模板方法设计模式的引用(第328页):
模板方法必须指定哪些操作是钩子(可以被重写)哪些操作是抽象操作(必须被重写)
这是一个很好的命令事件识别质量!如果您发现自己编写的.NET事件需要一个处理程序,或者不能有多个处理程序,那么这很好地表明.NET事件可能是错误的解决方案
如果您有模板方法的情况,那么通常某种形式的解决方案就足够了:
interface IDetails { Task ProcessAsync(); }
class Subject
{
private IDetails _details { get; }
public Subject(IDetails details) { _details = details; }
async Task SomeMethodAsync()
{
...
if (_details)
await _details.ProcessAsync();
}
}
知道了。通知/命令观察者/模板的区别抓住了我的直觉,即这不是理想的方法。我很少需要多播,所以模板模式更适合。但是,如果我重新做一遍的话,我会使用函数方法而不是接口,与其说是较小的耦合,不如说是较轻的重量。