Scala 在递归循环中处理和共享Monix任务的状态

Scala 在递归循环中处理和共享Monix任务的状态,scala,monix,Scala,Monix,我有下面的代码,它递归地迭代并通过网络执行某些操作。当它通过网络传输时,我想做一些优化,其中第一个优化是避免通过网络传输我已经尝试过的某些元素 例如,在下面的例子中,我调用一个URL,提取在该URL中找到的HREF,调用这些URL并报告状态。由于可能会再次获取某些URL,对于那些失败的URL,我希望将它们添加到全局状态,以便下次遇到此URL时,我将避免这些网络调用 代码如下: def callURLWithCache(url: String): Task[HttpResult] = {

我有下面的代码,它递归地迭代并通过网络执行某些操作。当它通过网络传输时,我想做一些优化,其中第一个优化是避免通过网络传输我已经尝试过的某些元素

例如,在下面的例子中,我调用一个URL,提取在该URL中找到的HREF,调用这些URL并报告状态。由于可能会再次获取某些URL,对于那些失败的URL,我希望将它们添加到全局状态,以便下次遇到此URL时,我将避免这些网络调用

代码如下:

def callURLWithCache(url: String): Task[HttpResult] = {
    Task {
      Http(url).timeout(connTimeoutMs = 1000, readTimeoutMs = 3000).asString
    }.attempt.map {
      case Left(err) =>
        println(s"ERR happened ----------------- $url ************************ ${err.getMessage}")
        // Add to the cache
        val httpResult = HttpResult(source = url, isSuccess = false, statusCode = 1000, errorMessage = Some(err.getMessage))
        val returnnnn: Try[Any] = httpResultErrorCache.put(url)(httpResult)
        httpResult
      case Right(doc) =>
        if (doc.isError) {
          HttpResult(source = url, isSuccess = doc.isSuccess, statusCode = doc.code)
        } else {
          val hrefs = (browser.parseString(doc.body) >> elementList("a[href]") >?> attr("href"))
            .distinct.flatten.filter(_.startsWith("http"))
          HttpResult(source = url, isSuccess = doc.isSuccess, statusCode = doc.code, elems = hrefs)
        }
    }
  }
您可以在case Left(..)块中看到,我将failed case类添加到缓存中,该缓存在该函数的封闭类上全局定义为:

val underlyingCaffeineCache: cache.Cache[String, Entry[HttpResult]] = Caffeine.newBuilder().maximumSize(10000L).build[String, Entry[HttpResult]]
implicit val httpResultErrorCache: Cache[HttpResult] = CaffeineCache(underlyingCaffeineCache)
下面是我执行递归操作的函数:

def parseSimpleWithFilter(filter: ParserFilter): Task[Seq[HttpResult]] = {
    def parseInner(depth: Int, acc: HttpResult): Task[Seq[HttpResult]] = {
      import cats.implicits._
      if (depth > 0) {
        val batched = acc.elems.collect {
          case elem if httpResultErrorCache.get(elem).toOption.exists(_.isEmpty) =>
            callURLWithCache(elem).flatMap(newElems => parseInner(depth - 1, newElems))
        }.sliding(30).toSeq
          .map(chunk => Task.parSequence(chunk))
        Task.sequence(batched).map(_.flatten).map(_.flatten)
      } else Task.pure(Seq(acc))
    }
    callURLWithCache(filter.url).map(elem => parseInner(filter.recursionDepth, elem)).flatten
  }
可以看出,我正在检查作为当前元素的url是否已经在缓存中,这意味着我已经尝试过了,但失败了,因此我希望避免再次对其进行HTTP调用


但是发生的是,httpResultErrorCache总是空的。我不确定任务块是否导致了这种行为。关于如何让缓存工作有什么想法吗?

您似乎只使用
isSuccess=false
将内容放入缓存,但只有
get
一次才具有
isSuccess=true
。。。另外,看起来您正在并行运行30个任务,因此,这些任务可能无法访问彼此的结果。我猜并发性是其背后的罪魁祸首。这有什么办法吗?我认为您混淆了case类中的isSuccess到TryOh中的isSuccess。。。是 啊不要像这样使用
试试
,这会让人非常困惑。只要执行
如果cache.get(elem).toOption.exists(u.isEmpty)
@Dima会将状态作为引用传递给CallurWithCache并将其返回到parseInner工作中吗?你怎么看?另一件事是你确定你想使用
滑动
而不是
分组
?您似乎多次处理同一元素。。。也许,这就是你的问题?如果您看到来自同一url的多个错误,这可能就是为什么-两个不同的块并行执行此操作。。。