合并多个接收源

时间:2018-11-30 16:43:20

标签: java multithreading kotlin rx-java reactive-programming

让我们想象一下以下情况,我们有一个从服务器端返回Observable对象源的函数:

private fun getStatistics(): Observable<TestStatistics> {
        return Observable
                .fromIterable(listOf(
                        TestStatistics(1.1, 1.2, 4),
                        TestStatistics(2.1, 2.2, 1),
                        TestStatistics(3.1, 3.2, 99)
                ))
                .delay(2, TimeUnit.SECONDS)
                .subscribeOn(Schedulers.io())
    }

TestStatistics实体:

data class TestStatistics(val doubleCashBack: Double, val doubleAmount: Double, val currencyId: Int)

如您在服务器响应中看到的,我们有currencyId指向货币实体:

data class TestCurrency(val currencyId: Int, val currencySign: String)

我们还有另一个函数,可以从数据库中按ID返回货币实体的单一来源:

private fun getCurrencyById(id: Int): Single<TestCurrency> {
        return when (id) {
            1 -> Single.just(TestCurrency(1, "!"))
            2 -> Single.just(TestCurrency(2, "@"))
            3 -> Single.just(TestCurrency(3, "#"))
            else -> Single.error(Exception("Currency not found"))
        }
                .delay(1, TimeUnit.SECONDS)
                .subscribeOn(Schedulers.io())
    }

主要思想是获取每个发出的Statistic实体,将它们的属性组合在一起,而不是将包含属性和Currency的组合实体作为对象,因此出现了问题,在这种情况下,我们必须采用currencyId作为第一个成功接收的Currency数据库中的对象,然后发出结果实体,因此结果类如下所示:

data class TestDashboardStatistics(val count: Int, val cashBack: Double, val amount: Double, val testCurrency: TestCurrency)

但是这种可观察到的资源组合存在一些问题,服务器请求在一个线程中运行,数据库在另一个线程中运行,并且在第三线程中组合代码,因此我必须确保我将处理从服务器接收到的所有统计信息,并将忽略所有错误从数据库返回(仅当我最终找到货币时,如果所有请求均失败,则必须返回默认值),并且仅向数据库发出一个成功请求,将此对象放入结果实体并将其返回 梳理功能可能如下:

private fun getCombinedStatistics(): Single<TestDashboardStatistics> {
        return Single.create<TestDashboardStatistics> {
            var transactionsAmount = 0.0
            var cashBackAmount = 0.0
            var count = 0
            var currency = TestCurrency(-1, "default")

            getStatistics().subscribe({ statistic ->
                ++count
                transactionsAmount += statistic.doubleAmount
                cashBackAmount += statistic.doubleCashBack
                getCurrencyById(statistic.currencyId).subscribe({ cur ->
                    // TODO do not request currency for future statistics because we have it now but
                    // TODO because different threads we can subscribe for new request before we will receive this result
                    currency = cur
                }, { err ->
                    // TODO ignore error if there is a hope that other statistics will have valid currency code
                })
            }, {
                // On requesting statistics error just throw it up
                Single.error<TestDashboardStatistics>(it)
            }, {
                // When all statistics will be received and precessed emit result
                // But it could be called even before we will receive any response from database
                Single.just(TestDashboardStatistics(count, cashBackAmount, transactionsAmount, currency))
            })
        }
    }

我想到的一个解决方案是以某种方式从数据库请求货币,从而阻塞了处理统计信息,因此处理将一直等到db请求完成后再转到另一个,但是我对Rx运算符的了解很差,所以我不知道该怎么办。

1 个答案:

答案 0 :(得分:0)

我建议按照您的建议将数据库请求保留为阻塞状态

data class TestStatistics(val doubleCashBack: Double, val doubleAmount: Double, val currencyId: Int)
data class TestCurrency(val currencyId: Int, val currencySign: String)
data class TestDashboardStatistics(val count: Int?, val cashBack: Double, val amount: Double, val testCurrency: TestCurrency)

object Helloworld {
    private fun getStatistics(): Observable<TestStatistics> {
        return Observable
            .fromIterable(listOf(
                TestStatistics(1.1, 1.2, 4),
                TestStatistics(2.1, 2.2, 1),
                TestStatistics(3.1, 3.2, 99),
                TestStatistics(4.1, 4.3, 2),
                TestStatistics(5.1, 5.3, 3)
            ))
            .delay(2, TimeUnit.SECONDS)
    }

    private fun getCurrencyById(id: Int): TestCurrency? {
        // blocking call
        return when (id) {
            1 -> TestCurrency(1, "!")
            2 -> TestCurrency(2, "@")
            3 -> TestCurrency(3, "#")
            else -> null
        }
    }

    @JvmStatic
    fun main(args: Array<String>) {
        getStatistics()
            .map { getCurrencyById(it.currencyId) to it }
            .filter { it.first != null }
            .map { TestDashboardStatistics(null, it.second.doubleCashBack, it.second.doubleAmount, it.first!!) }
            .subscribe { println(it) }

        Thread.sleep(5000)
    }
}

我将count字段设置为可空,因为我不完全了解您要实现的目标。

我还建议您不要使用辅助方法中的subscribeOn调用,而是将其放入main方法中(连同observeOn()函数一起使用),在此您可以链接业务逻辑。这样,您可以在不同的操作之间切换线程(例如,在ui线程上进行订阅,在io线程上进行数据库调用,在computation线程上执行繁重的算法,等等)

希望这会有所帮助:)

P.S。据我了解您的用例,您所需要的只是一个简单的map操作:TestStatistics-> TestDashboardStatistics。如果不想每次都TestCurrency进入数据库,则可以缓存已经获取的实例(使用Map?)。