I have the following class
public class Foo
{
private string? _bar;
public event EventHandler<CancelPropertyChangingEventArgs>? CancelPropertyChanging;
public string? Bar
{
get => _bar;
set
{
if (CancelPropertyChanging is { } cancelPropertyChanging)
{
var eventArgs = new CancelPropertyChangingEventArgs()
{
Cancel = false,
NewValue = value,
OldValue = _bar,
PropertyName = nameof(Bar),
};
cancelPropertyChanging(this, eventArgs);
if (eventArgs.Cancel)
return;
}
_bar = value;
}
}
public override string ToString() => Bar ?? "";
}
Where I can register to the event CancelPropertyChanging
and potentially cancel a setter.
Everything works as expected when no async/await
is involved.
With the following code.
var foo = new Foo();
foo.Bar = "Init Value";
foo.CancelPropertyChanging += Foo_CancelPropertyChanging;
foo.Bar = "Hello World";
foo.Bar = "Hello World 2";
Console.WriteLine(foo.Bar);
Console.WriteLine(foo.Bar == "Hello World 2" ? "Error" : "Correct");
void Foo_CancelPropertyChanging(object? sender, CancelPropertyChangingEventArgs e)
{
Console.WriteLine($"Changing Sync - OldValue: {e.OldValue} | NewValue: {e.NewValue}");
if (Convert.ToString(e.NewValue) == "Hello World 2")
e.Cancel = e.Cancel || true;
}
I am getting this output:
Changing Sync - OldValue: Init Value | NewValue: Hello World
Changing Sync - OldValue: Hello World | NewValue: Hello World 2
Hello World
Correct
So I did successfully Cancel
the setting of Hello World 2
into the Bar
property of my Foo
object.
The same code will fail when I declare the event handler async
and introduce a await Task.Delay(1_000);
How could I await
for all event handlers to really finish even if they are declared as async
?
There isn't really a way to just say
await cancelPropertyChanging(this, eventArgs);
I wouldn't know if someone somewhere would register an event handler and mark it async
and does what not, the second this happens my code will give undesired results.
Here you may find a demo of the code above:
https://dotnetfiddle.net/GWhk3w
Notice:
That this code demonstrates the issue at hand in the easiest setup I could think of, the acutal issue is more meaning full than a cancable setter, but it revolves around events and the ability to cancel, where I am facing the wrong cancel signal.
Edit:
A maybe realistic example would be.
Imagine you have a WPF window and register to the Closing
event, which has a Cancel
member.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.window.closing?view=windowsdesktop-6.0
No one would stop me from writing this
async void WpfWindow_Closing(object sender, CancelEventArgs e)
{
await Task.Delay(10_000);
e.Cancel = true;
}
How does the Window
- if it actually does - wait for this code to finish to know if I set the Cancel
member, and actually cancel the close of the window.