Swift 如何防止PassthroughSubject在并发上游期货完成之前杀死.sink?

Swift 如何防止PassthroughSubject在并发上游期货完成之前杀死.sink?,swift,ios13,combine,Swift,Ios13,Combine,我有一个PassthroughSubject,它发送30个整数,后跟一个finish消息 当我从主体那里收到这些数字时,我产生了一个睡眠一秒钟的未来,并以输入数字*2完成 我使用.receiveOn确保futures并发运行,但这意味着finish消息也会通过链并发传播 在所有期货交易结束前结束 任何RxSwift/联合收割机向导都知道我如何才能使完成消息的接收被延迟 以下是实现所述行为的游乐场: import Foundation import Combine import Playgroun

我有一个PassthroughSubject,它发送30个整数,后跟一个finish消息

当我从主体那里收到这些数字时,我产生了一个睡眠一秒钟的未来,并以输入数字*2完成

我使用.receiveOn确保futures并发运行,但这意味着finish消息也会通过链并发传播 在所有期货交易结束前结束

任何RxSwift/联合收割机向导都知道我如何才能使完成消息的接收被延迟

以下是实现所述行为的游乐场:

import Foundation
import Combine
import PlaygroundSupport

/// Setting up the playground
PlaygroundPage.current.needsIndefiniteExecution = true

/// Injects numbers 0-30 into combine message stream, and then sends a finish.
func publishNumbers(to subject: PassthroughSubject<Int, Error>) {
    (0..<30).forEach {
        subject.send($0)
    }
    subject.send(completion: .finished)
}
/// Delays for one secont, and completes the future by doubling the input.
func delayAndDoubleNumber(_ int: Int) -> Future<Int, Error> {
    return Future<Int, Error> { complete in
        sleep(1)
        complete(.success(int * 2))
    }
}

// Properties involved in Combine processing chain.
let numbersSubject = PassthroughSubject<Int, Error>()
let processingQueue = DispatchQueue.global(qos: .userInitiated)


// Combine processing chain
numbersSubject
    .receive(on: processingQueue) //Comment this line to observe that all futures finish, and are collected before the finish message kills the sink.
    .flatMap { number in
        return delayAndDoubleNumber(number)
    }
    .collect(4)
    .sink(receiveCompletion: { completion in
        print("Complete: \(completion)")
    }, receiveValue: { value in
        print("Received Value: \(value)")
    })

publishNumbers(to: numbersSubject)

<代码>导入基础 进口联合收割机 导入PlaygroundSupport ///设置操场 PlaygroundPage.current.NeedsDefiniteExecution=true ///将数字0-30注入组合消息流,然后发送一个finish。 func PublishNumber(主题:传递主题){ (0)未来{ 返回未来{complete in 睡眠(1) 完成(.success(int*2)) } } //联合收割机处理链中涉及的属性。 let numbersubject=PassthroughSubject() 让processingQueue=DispatchQueue.global(qos:.userInitiated) //联合加工链 数字主题 .receive(on:processingQueue)//注释此行,以观察在finish消息终止接收器之前,是否已收集所有未来和。 .flatMap{中的数字 返回delayAndDoubleNumber(数字) } .收集(4) .sink(receiveCompletion:{completion in 打印(“完成:\(完成)”) },receiveValue:{中的值 打印(“接收值:\(值)”) }) PublishNumber(收件人:numbersSubject)
免责声明,这可能是对文档的错误解释,但我认为您应该使用
订阅(on:)
操作符,而不是
接收(on:)

:

与影响下游消息的receive(on:options:)不同,subscribe(on:)更改上游消息的执行上下文

我对此的解释是,如果希望队列中发出来自
numbersubject
的事件,可以使用
subscribe(on:)
,例如:

numbersSubject
    .flatMap { number in
        return delayAndDoubleNumber(number)
    }
    .collect(4)
    .subscribe(on: processingQueue)
    .receive(on: RunLoop.main)
    .sink(receiveCompletion: { completion in
        print("Complete: \(completion)")
    }, receiveValue: { value in
        print("Received Value: \(value)")
    })

从Xcode 11 beta 3开始,您不能将并发队列与Combine一起使用。您应该能够在Xcode 11 GM之前使用

菲利普·豪斯勒(Philippe Hausler)是一位从事联合收割机工作的苹果工程师。他在上说:

另外值得注意的是,用作调度器的
调度队列
必须始终是串行的,以遵守联合收割机操作员的合同

他说:

因此,接下来,在传播下游事件的方式方面有一些变化。现在,我们能够满足1.03的约束,即使DispatchQueue是并发的,或者OperationQueue不是maxConcurrentOperations的限制1,或者任何有效的调度程序是并发的,我们将始终在请求的调度程序上为
.receive(on:)
发送序列化事件。我们稍微偏离规范的另一个警告是,在我们的世界中,诸如
cancel()
request(:)
等上游事件可以同时发生。也就是说,我们确实以线程安全的方式处理它们

您可以在Xcode 11 beta 3中通过调度到并发队列,然后从您的
未来
的闭包中返回到主队列来实现并发:

import Foundation
import Combine
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

func delayAndDoubleNumber(_ int: Int) -> Future<Int, Never> {
    return Future<Int, Never> { complete in
        DispatchQueue.global(qos: .userInitiated).async {
            sleep(1)
            DispatchQueue.main.async {
                complete(.success(int * 2))
            }
        }
    }
}

let subject = PassthroughSubject<Int, Never>()

subject
    .flatMap { delayAndDoubleNumber($0) }
    .collect(4)
    .sink(
        receiveCompletion: { print("Complete: \($0)") },
        receiveValue: { print("Received Value: \($0)") })

let canceller = (0 ..< 30).publisher().subscribe(subject)
<代码>导入基础 进口联合收割机 导入PlaygroundSupport PlaygroundPage.current.NeedsDefiniteExecution=true func delayAndDoubleNumber(int:int)->Future{ 返回未来{complete in DispatchQueue.global(qos:.userInitiated).async{ 睡眠(1) DispatchQueue.main.async{ 完成(.success(int*2)) } } } } let subject=PassthroughSubject() 主题 .flatMap{delayAndDoubleNumber($0)} .收集(4) .水槽( receiveCompletion:{打印(“完成:\($0)”)}, 接收值:{print(“接收值:\($0)”)}) 让取消器=(0..<30).publisher().subscribe(主题)
与Rob提出的解决方案不同,在接收器上调用cancel似乎不会取消任何内容。接收器仍然会接收所有值,并处理完成消息。哇,感谢链接到swift.org board。我不知道Beta在此阶段的串行队列要求。此解决方案有效s、 但它并不是唯一的combine-y。我想留下一个问题,以防combine的功能在今年秋天发布之前发生变化。