Использование yield из обработчика событий

У меня есть метод Foo.LongRunningMethod(), который выполняет очень сложную обработку, которая может продолжаться долго. По пути он стреляет Foo.InterestingEvent всякий раз, когда он сталкивается с определенным условием. Я хотел бы иметь возможность выставить перечисление этих событий, и я хотел бы иметь возможность начать итерации до LongRunningMethod на самом деле заканчивается. Другими словами, я хочу что-то вроде этого:

public IEnumerable<InterestingObject> GetInterestingObjects()
{
    foo.InterestingEvent += (obj) => { yield return obj; }
    foo.LongRunningMethod();

    yield break;
}

Это не работает, однако, по разумной причине, что вы не можете yield return от анонимного метода (и потому, что метод, использующий yield не может вернуться void, что делает наш обработчик событий). Есть ли другая идиома, которая позволяет мне сделать это? Или это просто плохая идея?

3 ответа

Решение

Эта цитата из ответа Тима Робинсона заставила меня задуматься:

То, что вы делаете, это превращение последовательности push (вхождения события) в pull (вызов в IEnumerable).

Превратить последовательность push в последовательность pull сложно, и корень моих проблем здесь. Но обратное (превращение последовательности вытягивания в последовательность выталкивания) тривиально, и это понимание дало мне решение. Я изменился LongRunningMethod во внутреннюю перечисляемую версию с тривиальным рефакторингом замены каждого обратного вызова события yield return и добавив yield break в конце. Потом я перевернул существующий LongRunningMethod в оболочку, которая просто запускает событие для всего возвращенного:

internal IEnumerable<InterestingObject> FindInterestingObjects() 
{ 
    /* etc */ 
}

public void LongRunningMethod()
{
    foreach (var obj in FindInterestingObjects())
    {
        OnInterestingEvent(obj);
    }
}

Это сохраняет общедоступный интерфейс, давая мне аккуратное перечисление, которое я могу использовать для сценариев, которые требуют этого. Как существенный побочный эффект, это также позволяет мне заблаговременно отказаться от длинных вычислений, если я этого захочу, что будет трудно сделать с версиями на основе событий или многопоточными.

Вы хотите иметь возможность подписаться на поток событий, которые приходят из LongRunningMethod и, когда происходит событие, выведите другое значение из IEnumerable? Вы можете найти полезные.NET Reactive Extensions: http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx

Реактивные расширения дают вам IObservable, который в действительности только толчок IEnumerable, Вы можете создать IObservable обертка вокруг события (например, вашего InterestingEvent) и выполнить оттуда обработку в стиле перечислимого типа (например, получение потока объектов).

Изменить: "Есть ли идиома, которая позволяет мне достичь этого", кроме принятия новой библиотеки от Microsoft? То, что вы делаете, это превращение последовательности push (появления события) в последовательность pull (вызывает IEnumerable).

Тяги и толчки, вероятно, не будут скоординированы, поэтому тебе нужно где-то буферизовать новые значения, которые были переданы до того, как была сделана тяга. Самым простым способом может быть принятие соглашения производитель-потребитель: подтолкнуть их к List<T> это потребляется абонентом GetInterestingObjects,

В зависимости от того, как генерируются события, может потребоваться поместить производителя и потребителя в отдельные потоки. (Все это то, что в итоге делают реактивные расширения, когда вы просите его преобразовать между IObservable и IEnumerable.)

Я бы побежал LongRunningMethod() в отдельном потоке, взаимодействующем с основным потоком: обработчик событий выдвигает InterestingObjects в какую-то синхронизированную очередь и сигнализирует основному потоку, когда приходит новое значение.

Основной поток ожидает объекты в очереди и возвращает их, используя yield вернуть их.

В качестве альтернативы вы также можете блокировать вложенную ветвь каждый раз, когда возникает событие, пока основной поток не вернет значение и получательIEnumerable запрашивает следующее значение.

Другие вопросы по тегам