Как я могу вернуть задачу из обработчика событий? - PullRequest
0 голосов
/ 02 мая 2020

Итак, у меня есть метод, который вызывает методы из другого класса и ожидает их завершения, прежде чем вызывать другой метод. У меня проблема в том, что вызываемый метод использует обработчик событий для выполнения действий. Есть ли способ отложить возврат этого метода до тех пор, пока обработчик событий не выполнит свою задачу? Чтобы уточнить, я сделал пример:

Основной класс:

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

Есть ли способ, которым я могу решить эту проблему. может быть, мой подход совершенно неверен. Я надеюсь, что кто-то может помочь мне с этим

1 Ответ

1 голос
/ 03 мая 2020

возможно, мой подход совершенно неверен

Всякий раз, когда у вас возникает ситуация, когда вы хотите await обработчик событий async, это красный флаг.

События являются допустимым дизайном, используемым при реализации шаблона проектирования Observer . Тем не менее, они иногда неправильно используются в качестве дизайна при реализации шаблонов Strategy или Template Method . В синхронном мире вы можете избежать подобных замен - используя событие вместо интерфейса с одним методом. Но в асинхронном мире такая замена не работает.

Еще один хороший лакмусовый тест - рассмотреть ваше событие и спросить: «Действительно ли имеет смысл иметь несколько обработчиков?» Если ответ отрицательный, то, во-первых, событие не является правильным дизайном.

Тем не менее, если вы действительно хотите «асинхронное событие» вместо более правильного интерфейса с асинхронный метод, тогда есть пара опций . Один из них - вернуть делегату события Task. Вы можете получить список обработчиков вручную и вызвать каждый из них. Вам просто нужно решить, хотите ли вы запускать все обработчики одновременно или выполнять их по одному. (И опять-таки, решение «мне нужно подождать их по одному» является убедительным признаком того, что событие - неправильный выбор дизайна).

Другой вариант - использовать «отсрочка», пропагандируемая UWP. Это позволяет использовать «нормальные» обработчики событий async void, если они получают объект отсрочки. Вы можете сделать что-то подобное, используя DeferralManager из 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();
}
...