【问题标题】:Wait for eventhandlers, if it is declared async等待事件处理程序,如果它被声明为异步
【发布时间】:2022-03-09 06:08:18
【问题描述】:

我有以下课程

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 ?? "";
}

我可以在哪里注册事件CancelPropertyChanging 并可能取消设置器。

当不涉及async/await 时,一切都会按预期进行。

使用以下代码。

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;
}

我得到这个输出:

Changing Sync - OldValue: Init Value | NewValue: Hello World
Changing Sync - OldValue: Hello World | NewValue: Hello World 2
Hello World
Correct

所以我成功地将Hello World 2 的设置Cancel 设置到我的Foo 对象的Bar 属性中。

当我声明事件处理程序async 并引入await Task.Delay(1_000); 时,相同的代码将失败

我怎么能await 让所有事件处理程序真正完成,即使它们被声明为async

没有办法直接说出来

await cancelPropertyChanging(this, eventArgs);

我不知道某个地方是否有人会注册一个事件处理程序并将其标记为 async 并且不做什么,如果发生这种情况,我的代码会产生不希望的结果。

您可以在这里找到上述代码的演示:

https://dotnetfiddle.net/GWhk3w

注意:

这段代码在我能想到的最简单的设置中演示了手头的问题,实际问题比 cancable setter 更有意义,但它围绕事件和取消能力,我面临错误的取消信号。

编辑:

可能是一个现实的例子。

假设您有一个 WPF 窗口并注册到具有 Cancel 成员的 Closing 事件。

https://docs.microsoft.com/en-us/dotnet/api/system.windows.window.closing?view=windowsdesktop-6.0

没有人会阻止我写这篇文章

async void WpfWindow_Closing(object sender, CancelEventArgs e)
{
    await Task.Delay(10_000);
    e.Cancel = true;
}

Window 如何 - 如果确实如此 - 等待此代码完成以了解我是否设置了 Cancel 成员,并实际取消关闭窗口。

【问题讨论】:

  • EventHandler&lt;T&gt; 不返回任何内容,因此不会有任何等待。即使它要返回Task,您也可能会将多个事件处理程序附加到同一个事件,并且它只会返回最后一个。将异步事件处理程序称为“即发即弃”是有原因的。简单地说,你不能为此使用EventHandler&lt;T&gt;
  • 此外,无论如何在属性设置器中都没有安全的方法来调用它,因为属性不能有异步访问器。因此,您将不得不摆弄同步而不是异步,使用诸如.ConfigureAwaiter(false).GetAwaiter().GetResult() 之类的“技巧”,这可能会导致死锁。不建议这样做。简而言之,异步事件处理程序将不适合您。
  • 你可能正在寻找这样的东西:dotnetfiddle.net/VBjELQ。如果没关系,我会把它作为答案发布。
  • 附带说明,b || truetrue,所以 e.Cancel = e.Cancel || true; 可以简化为 e.Cancel = true;。以这种方式进行逻辑组合的唯一原因是如果您有另一个变量可以具有任一值。在此期间需要考虑的事情。
  • 有趣。我还没有读过博客。如果 Stephen Cleary 已回答在链接的博客中介绍,我不会写答案。

标签: c# async-await event-handling task


【解决方案1】:

窗口(如果确实如此)如何等待此代码完成以了解我是否设置了 Cancel 成员,并实际取消窗口的关闭。

没有。

正常模式是总是取消关闭窗口(可能用“隐藏”代替关闭),执行(异步)操作,然后执行实际关闭.

即使声明为异步,我如何才能等待所有事件处理程序真正完成?

嗯,there are ways to do this,正如我的博客所述。但事情是这样的:您的事件调用代码必须await 才能让所有处理程序以一种或另一种方式完成。因为你不能在属性设置器中awaitawaiting 事件处理程序对你没有任何好处。

与 WPF 解决方法一样,最好的解决方案可能是更广泛的重新设计。在这种情况下,您可以引入(一个队列)待处理更改的概念,这些更改仅在完成所有异步检查后才应用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-22
    相关资源
    最近更新 更多