如何从事件处理程序返回任务?

Posted

技术标签:

【中文标题】如何从事件处理程序返回任务?【英文标题】:How can i return a task from an eventhandler? 【发布时间】:2020-08-17 08:08:47 【问题描述】:

所以我有一个方法从另一个类调用方法,并在调用另一个方法之前等待它们完成。我遇到的问题是被调用的方法使用事件处理程序来执行操作。有没有办法延迟该方法的返回,直到事件处理程序完成它的事情?为了澄清,我举了一个例子:

主类:

class MainClass

    SomeObject someObject;

    private async void MainMethod()
    
        someObject = new SomeObject();

        await someObject.SomeMethod();
        await someObject.SomeOtherMethod();
    

SomeObject 类:

class SomeObject

    public event EventHandler<object> SomethingChanged;

    public async Task SomeMethod()
    
        Console.WriteLine("fired SomeMethod");

        SomethingChanged += SomethingChangedAsync;
        Subscribe("<someURL>", SomethingChanged); //this is being done by an api im using...

        await api.someApiCall;

        Console.WriteLine("here things are happening that would make the event trigger. the method however, is now done with its logic and now returns and instantly goes to SomeOtherMethod but SomethingChangedAsync is not done processing what needs to be done");
    

    public async Task SomeOtherMethod()
    
        await api.someApiCall;

        Console.WriteLine("fired SomeOtherMethod");
    

    private async void SomethingChangedAsync(object sender, object e)
    
        await api.someApiCall;
        Console.WriteLine("doing stuff here that takes some time. i would like SomeMethod to wait with returning until the logic here is finished");
    

有什么办法可以解决这个问题。也许我的方法是完全错误的。我希望有人能帮我解决这个问题

【问题讨论】:

你有很多异步方法声明,但我没有在这些方法的实现中看到 await 关键字。 在您的示例中大量滥用 async 关键字。 @Hardood 我的错。在实际程序中的这些方法中有等待调用,但我没有将它们包括在这里。生病更新问题 如果这是真正的代码会更容易。有很多奇怪的事情正在发生。如果MainMethod 应该等到SomethingChanged 事件被触发,那么为什么不让MainMethod 订阅该事件并在事件处理程序中调用SomeOtherMethod() 有点复杂。看看这个:How do I await events in C#? 【参考方案1】:

也许我的方法完全错误

当您遇到想要awaitasync 事件处理程序的情况时,这是一个危险信号。

事件是实现Observer design pattern 时使用的有效设计。但是,在实现Strategy 或Template Method 设计模式时,它们有时会被误用作设计。在同步世界中,您可以摆脱这种替换 - 使用事件代替具有单个方法的接口。但在异步世界中,这种替换就失效了。

另一个很好的试金石是考虑您的事件并询问“这真的有意义吗?它有多个处理程序?”如果答案是否定的,那么事件一开始就不是正确的设计。

也就是说,如果你真的想要一个“异步事件”而不是一个更合适的异步方法接口,那么there are a couple options。一种是让事件委托返回Task。您可以手动获取处理程序列表并调用每个处理程序。您只需要决定是要同时启动所有处理程序还是一次执行一个。 (同样,这里的“我需要一次等待一个”的决定强烈表明 事件 是错误的设计选择)。

另一种选择是使用由 UWP 推广的“延期”。这允许使用“正常”async void 事件处理程序,只要它们获取延迟对象。你可以使用DeferralManager from AsyncEx 来做这样的事情:

public sealed class MyEventArgs : EventArgs, IDeferralSource

    private readonly IDeferralSource _deferralSource;

    public MyEventArgs(IDeferralSource deferralSource)
    
        _deferralSource = deferralSource;
    

    public IDisposable GetDeferral() => _deferralSource.GetDeferral();


public event Action<object, MyEventArgs> MyEvent;

private async Task RaiseEventAsync()

    var deferralManager = new DeferralManager();
    var args = new MyEventArgs(deferralManager.DeferralSource);
    MyEvent?.Invoke(this, args);
    await deferralManager.WaitForDeferralsAsync();

那么任何async void 处理程序都应该获得这样的延迟:

private async void Handler(object source, MyEventArgs args)

    using var deferral = args.GetDeferral();
    await Task.Yield();

【讨论】:

以上是关于如何从事件处理程序返回任务?的主要内容,如果未能解决你的问题,请参考以下文章

为啥从代码中调用事件处理程序是不好的做法?

如何使事件处理程序异步运行?

如何从事件中删除所有事件处理程序

如何从事件中删除所有事件处理程序

netty之NioEventLoop事件循环处理

从事件处理程序确定发送者对象