0

So i have a method that calls methods from another class and is waiting for them to complete before calling another method. The problem i am having is that the method thats being called uses an eventhandler to do actions. Is there a way to delay the return of that method until the eventhandler has done its thing? To clarify, i made an example:

Main class:

class MainClass
{
    SomeObject someObject;

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

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

SomeObject Class:

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

is there a way i can fix this issue. maybe my approach is completely wrong. i hope someone can help me with this

Neoray
  • 71
  • 1
  • 6
  • You have a lot of async methods declaration, but i didnt see await keyword inside the implementation of these methods. – Hardood May 02 '20 at 21:03
  • A lot of misuse to async keyword over your example. – Hardood May 02 '20 at 21:05
  • @Hardood my bad. there are await calls in those methods in the actual program but i didnt include them here. ill update the question – Neoray May 02 '20 at 21:12
  • It would be easier if this was real code. There's a lot of weird stuff going on. If `MainMethod` should wait until the `SomethingChanged` event has been fired, then why not have `MainMethod` subscribe to that event and call `SomeOtherMethod()` inside the event handler? – Xerillio May 02 '20 at 21:36
  • 1
    It's a bit complicated. Check this out: [How do I await events in C#?](https://stackoverflow.com/questions/27761852/how-do-i-await-events-in-c) – Theodor Zoulias May 02 '20 at 21:41

1 Answers1

1

maybe my approach is completely wrong

Whenever you have the situation where you want to await an async event handler, that's a red flag.

Events are a valid design to use when implementing the Observer design pattern. However, they are sometimes misused as a design when implementing the Strategy or Template Method design patterns. In the synchronous world, you can get away with that kind of substitution - using an event in place of an interface with a single method. But in the asynchronous world, that kind of substitution breaks down.

Another good litmus test is to consider your event and ask "does it really make sense that this has multiple handlers?" If the answer is no, then an event is not the correct design in the first place.

That said, if you really want an "asynchronous event" instead of a more proper interface with an asynchronous method, then there are a couple options. One is to have the event delegate return a Task. You can get the list of handlers manually and invoke each one. You'll just need to decide if you want to start all handlers concurrently or execute them one at a time. (And again, a decision here of "I need to wait them one at a time" is a strong indication that an event is the wrong design choice).

Another option is to use a "deferral", as popularized by UWP. This allows "normal" async void event handlers to be used, as long as they acquire a deferral object. You could do something like this using the 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();
}

Then any async void handler is expected to acquire a deferral as such:

private async void Handler(object source, MyEventArgs args)
{
    using var deferral = args.GetDeferral();
    await Task.Yield();
}
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810