как ждать вызова обработчика событий? - PullRequest
1 голос
/ 24 сентября 2019

В основном у меня есть метод, который вызывает обработчик событий.обработчик событий вызывает асинхронный метод, и мне нужно знать результаты этого метода (true или false).так как обработчики событий могут возвращать только void, я создал свои собственные eventargs со свойством success, для которого я установил значение true в этом методе, если он работает правильно.

public virtual async Task<bool> TrySomething()
        {
            var args = new MyEventArgs();
            SomeEvent?.Invoke(this, args);
            return args.Success;
        }

SomeEvent подключен к SomeEventHandler

private async void SomeEventHandler(object sender, MyEventArgs e)
        {
            e.Success = await AnAsyncMethod();
        }

private asyc Task<bool> AnAsyncMethod()
{
  //...
}

Что меня смущает, так это то, есть ли какая-либо гарантия того, что метод TrySomething будет ожидать завершения SomeEvent, чтобы был установлен параметр Success, прежде чем возвращать его?И если нет, то как я могу убедиться, что это происходит?

Спасибо

Ответы [ 3 ]

3 голосов
/ 24 сентября 2019

есть ли гарантия, что метод TrySomething будет ожидать завершения SomeEvent, чтобы был установлен параметр Success, прежде чем его вернуть?

Нет.async void означает «не уведомлять звонящего, когда я закончу».Таким образом, код, вызывающий ваше событие, не может знать, когда обработчик события завершился, если вы сами не напишите эту дополнительную логику.

И если нет, то как я могу гарантировать, что это происходит?

Ну, это более сложный вопрос.События .NET имеют вид , который я называю «событиями уведомления» - то есть, когда событие запускается, оно уведомляет всех своих слушателей.Нет необходимости «ждать», потому что у слушателя нет возможности отправить отзыв уведомителю.

Ваш код является примером того, что я называю «командным событием» - кодом, которыйevent, но не соответствует семантике события уведомления.Ваш код требует ответа от обработчика.

Итак, первый вопрос, который вам нужно задать себе: «Действительно ли я хочу, чтобы это было событием?»Один хороший лакмусовый тест для этого: «Могу ли я определить значимую семантику, если есть несколько обработчиков?»

Более конкретно, как должен вести себя ваш код, если несколько обработчиков подключены к событию?Возможно, ответ «это не имеет смысла».Или, возможно, ответ таков: «Я хочу подождать, пока все из них завершатся и будут« успешными », только если все они« успешны »».Или «жди всех и будь« успешным », если любые из них являются« успешными »».Или «дождитесь завершения первого и используйте этот результат».Или «подождите, пока они завершат по одному, останавливаясь на первом успехе».Или «подождите, пока они завершат по одному, останавливаясь на первом сбое».Это тот выбор, который сразу приходит на ум;их может быть больше.

Если ответ «это не произойдет в моем коде» или «более одного обработчика не имеет смысла» или «это слишком сложное решение, чтобы принять его прямо сейчас»тогда соответствующий ответ: удалить event.Это не событие.Это вызов метода.В терминологии шаблона проектирования events используются для реализации шаблона наблюдателя , но у вас есть шаблон стратегии , и поэтому event sплохо подходят.В этом случае вы могли бы использовать ответ Габриэля или что-то подобное, когда вы определяете стратегию с использованием интерфейса, и вместо того, чтобы вызывать событие, вы вызываете метод для этого интерфейса.

Однако,если действительно имеет смысл иметь несколько обработчиков, и - это смысловая семантика, которую вы можете использовать, тогда вам нужно изменить тип EventArgs, чтобы иметь какое-то "сборщик ответов, а затем попросите ваш код для сбора событий интерпретировать эти ответы.Точный код будет зависеть от необходимой вам семантики.

1 голос
/ 24 сентября 2019

Вы можете объявить свойство Success как Task<bool> вместо bool.Затем присвойте его внутри обработчика следующим образом:

private void SomeEventHandler(object sender, MyEventArgs e)
{
    e.Success = AnAsyncMethod(); // Without await
}

В конце вы сможете получить результат после вызова события и ожидания свойства.

public virtual async Task<bool> TrySomething()
{
    var args = new MyEventArgs();
    SomeEvent?.Invoke(this, args);
    return await (args.Success ?? Task.FromResult(false));
}
1 голос
/ 24 сентября 2019

Вся причина async void разрешена для обработчиков событий.Из документации Типы асинхронного возврата :

Вы используете тип возврата void в обработчиках асинхронных событий, для которых требуется тип возврата void.

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

События не предназначены для того, чтобы быть «эй, это случилось, что мне делать дальше?»Таким образом, не должно быть в ожидании обработчика события.

Если возвращение args.Success зависит от успешного завершения SomeEventHandler, то это не должен быть обработчик события.Вместо этого вы можете иметь свойство Func<Task<bool>> (функция, которая возвращает Task<bool>).Примерно так:

public class SomeClass {
    private Func<Task<bool>> IsSuccessful;

    public SomeClass(Func<Task<bool>> isSuccessful) {
        // Accept a reference to a function and store it
        IsSuccessful = isSuccessful;
    }

    public async Task<bool> DoSomething() {
        // Call our function and return the result
        return await IsSuccessful();
    }
}

Тогда вы можете использовать это так:

// This is the method we want it to call
private async Task<bool> AnAsyncMethod() {
  await Task.Delay(1);
  return true;
}

// so we pass it in the constructor of the class.
// You don't have to pass it in the constructor - this is just an example
var myClass = new SomeClass(AnAsyncMethod);

Таким образом, очень ясно, что SomeClass не может функционировать должным образом без вызова этого метода и, следовательно, ровно одна реализация этого метода должна быть передана в класс.

...