Использование 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()
в отдельном потоке, взаимодействующем с основным потоком: обработчик событий выдвигает InterestingObject
s в какую-то синхронизированную очередь и сигнализирует основному потоку, когда приходит новое значение.
Основной поток ожидает объекты в очереди и возвращает их, используя yield
вернуть их.
В качестве альтернативы вы также можете блокировать вложенную ветвь каждый раз, когда возникает событие, пока основной поток не вернет значение и получательIEnumerable
запрашивает следующее значение.