Reactivecocoa: как обеспечить обратное давление (ленивый производитель сигналов, который извлекает больше данных по мере их использования)

Созданный на 20 апр. 2016  ·  6Комментарии  ·  Источник: ReactiveCocoa/ReactiveCocoa

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

Есть идеи, как я могу достичь того, чего хочу? Это вообще возможно?

Спасибо!

public func streamMessages(from startOffset: Offset = Offset(value: 0), toExclusive endOffsetOpt : Offset? = .None,
                               includeTransient: Bool = true) -> SignalProducer<Message, NoError> {

        func streamMessagesChunk(from: Offset) -> SignalProducer<Message, NoError> {
            func waitForNewMessageAvailable(from: Offset) -> SignalProducer<Offset?, NoError> {
                return self.lastOffsetIncludingTransient(includeTransient).producer
                    .filter{ offsetOpt in offsetOpt.map {offset in offset >= from } ?? false }
                    .take(1)
            }

            let streamMessagesProducer = self.fetchMessages(10, from: from, includeTransientMessages: includeTransient)
                .flatMap(.Concat){ messages in SignalProducer<Message, NoError>(values: messages)}

            return waitForNewMessageAvailable(from)
                .then(streamMessagesProducer)
        }

        func streamNextBatch(from: Offset, observer: Observer<Message, NoError>, observerDisposable: CompositeDisposable) -> Void {
            func hasReachedEndOffset(currentOffset: Offset) -> Bool {
                return endOffsetOpt.map{ endOffset in endOffset == currentOffset } ?? false
            }

            print("StreamNextBatch \(from)")

            streamMessagesChunk(from).startWithSignal { signal, signalDisposable in
                var lastOffset: Offset = from
                let disposableHandle = observerDisposable.addDisposable(signalDisposable)

                signal.observe { switch $0 {
                    case let .Failed(error): observer.sendFailed(error)
                    case .Interrupted: observer.sendInterrupted()
                    case .Completed:
                        disposableHandle.remove()
                        streamNextBatch(lastOffset.next, observer: observer, observerDisposable: observerDisposable)
                    case .Next(let message):
                        if hasReachedEndOffset(message.offset) {
                            disposableHandle.remove()
                            observer.sendCompleted()
                        } else {
                            lastOffset = message.offset
                            observer.sendNext(message)
                        }
                    }
                }
            }
        }

        return SignalProducer<Message, NoError> { observer, observerDisposable in
            streamNextBatch(startOffset, observer: observer, observerDisposable: observerDisposable)
        }
    }

func testShouldStreamMessagesWaitingForFutureMessages() {
        let expectation = self.expectationWithDescription("Test")
        let messages = (0...50000).map{value in self.createKafkaData(UInt64(value)) }
        let nextMessages = (50001...65000).map{value in self.createKafkaData(UInt64(value)) }

        try! self.sut.publishMessages(messages, persist: false).get()

        let messageCountFuture = self.sut
            .streamMessages(from: Offset(value: 45), toExclusive: Offset(value: 60000), includeTransient: true)
            .observeOn(QueueScheduler())
            .map{ m in print("sleeping at \(m.data)"); sleep(1); return 1 }
            .reduce(0, +)
            .toFuture()

        messageCountFuture.onSuccess{ count in
            expect(count) == 15
            expectation.fulfill()
        }

        try! self.sut.publishMessages(nextMessages, persist: false).get()

        self.waitForExpectationsWithTimeout(30, handler: nil)
    }

     func createKafkaData(number: UInt64) -> String {
        return "message \(number)"
    }
question

Все 6 Комментарий

_Извините за долгую задержку ответа. Мы работали над множеством старых проблем, так как некоторые участники отказались ._

К сожалению, AFAIK в настоящее время не может обеспечить противодавление между планировщиками. 😞

Знаете ли вы, есть ли способ сделать это с помощью каких-либо других реактивных фреймворков? Я определенно думаю, что это интересная концепция.

Насколько мне известно, фреймворки из семейства Rx обычно не поддерживают обратное давление, но одна конкретная реализация потоков (akka-streams) поддерживает его: http://www.smartjava.org/content/visualizing-back-pressure -и-реактивные-потоки-акка-потоки-статистика-графана-и-притокdb

Идея состоит в том, что данные идут вниз по потоку, а спрос - вверх, поэтому получатель всегда контролирует максимальную скорость входящих данных.

Также RxSwift, похоже, не имеет противодавления, но похоже, что их оператора defer было бы достаточно для удовлетворения тех же потребностей: http://reactivex.io/documentation/operators/defer.html

Насколько сложно было бы иметь такой оператор отсрочки в ReactiveCocoa?

Не трудно. Я сделал что-то подобное в Rex . Хотя, похоже, вы просите обобщенную версию. Вместо временного интервала для задержки вам понадобится сигнал запуска / параметр производителя для отсрочки подписки.

@neilpa нам нужно, чтобы подписка была отложена до тех пор, пока потребитель не закончит потребление потока. Это хорошо работает с ReactiveCocoa (пример выше), пока вы не начнете подписываться в другом планировщике.

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

Была ли эта страница полезной?
0 / 5 - 0 рейтинги