Использование yield из обработчика событий - PullRequest
2 голосов
/ 16 июля 2010

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

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

    yield break;
}

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

Ответы [ 3 ]

5 голосов
/ 16 июля 2010

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

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

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

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

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

2 голосов
/ 16 июля 2010

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

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

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

1 голос
/ 16 июля 2010

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

То, что вы делаете, это превращение последовательности 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);
    }
}

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

...