Kotlin从generateSequence()到flow(),但生成了错误的字节码

问题描述 投票:1回答:1

我有一个使用http请求来检索远程页面的功能。该页面可能具有一个或零个next页面的链接。如果要生成所有页面的链,则generateSequence是理想的解决方案。这是我所做的:

首先,有两个实用程序功能:

fun getBlockingDocument(url: String): Document?,顾名思义,它是一个阻止函数。该实现只是发送HTTP请求并解析到JSoup文档。

fun getNextIndexPage(doc: Document, url: String): String?,它也是一个阻止功能,但是它与网络无关,它只是解析以获取下一页,因此在这里阻止是可以的。

确定,这是序列代码:

val initUrl = // initial url

generateSequence(tools.getBlockingDocument(initUrl).let { initUrl to it }) { (url, doc) ->
  doc?.let {
    parser.getNextIndexPage(doc, url)
  }?.let { nextUrl ->
    nextIndexUrl to tools.getBlockingDocument(nextUrl)
  }
}.forEachIndexed { index, urlAndDoc ->
  val url = urlAndDoc.first
  logger.info("[{}] : {}", index, url)
}

它运作良好,并且正确地链接了所有页面。

但是如果我将网络调用更改为暂停功能怎么办?这是我创建的:

suspend fun getSuspendingDocument(url: String): Document?

我没有发现与generateSequence类似的flow构建器样本,所以我这样实现:

  @ExperimentalCoroutinesApi
  @Test
  fun testGetAllPagesByFlow() {
    val flow = flow<Pair<String, Document?>> {
      suspend fun generate(url: String) {
        tools.getSuspendingDocument(url)?.let { url to it }?.also { (url, doc) ->
          emit(url to doc)

          parser.getNextIndexPage(doc, url)?.also { nextUrl ->
            generate(nextUrl) // recursive
          }
        }
      }
      generate("http://...initial url here")
    } // flow

    runBlocking {
      flow.collectIndexed { index, urlAndDoc ->
        val url = urlAndDoc.first
        logger.info("[{}] : {}", index, url)
      }
    }
  }

我使用递归调用(fun generate())来emit在每个页面中找到的下一个URL。我不确定这是否是创建Flow的惯用方式,但是我没有找到类似的代码。如果您有更好/惯用的方式,请告诉我,非常感谢!

无论如何,我认为它应该可以工作,但是我的IDE(IntelliJ)抱怨wrong bytecode generated,这是我之前从未见过的。

Error:Kotlin: [Internal Error] org.jetbrains.kotlin.codegen.CompilationException: Back-end (JVM) Internal error: wrong bytecode generated
  @Lorg/jetbrains/annotations/Nullable;() // invisible
    // annotable parameter count: 1 (visible)
    // annotable parameter count: 1 (invisible)
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
   L0
   L1
    ALOAD 0
    GETFIELD destiny/data/FlowTest$testGetAllPagesByFlow$flow$1.p$ : Lkotlinx/coroutines/flow/FlowCollector;
    ASTORE 2
   L2
   L3
    LINENUMBER 44 L3
    NEW destiny/data/FlowTest$testGetAllPagesByFlow$flow$1$1
    DUP
    ALOAD 2
    ALOAD 3
    ACONST_NULL
    INVOKESPECIAL destiny/data/FlowTest$testGetAllPagesByFlow$flow$1$1.<init> (Lkotlinx/coroutines/flow/FlowCollector;Ldestiny/data/FlowTest$testGetAllPagesByFlow$flow$1$1;Lkotlin/coroutines/Continuation;)V
    ASTORE 3
   L4
   L5
    LINENUMBER 56 L5
    ALOAD 3
    CHECKCAST destiny/data/FlowTest$testGetAllPagesByFlow$flow$1$1
    ALOAD 0
    GETFIELD destiny/data/FlowTest$testGetAllPagesByFlow$flow$1.$initUrl : Ljava/lang/String;
    ALOAD 0
    ALOAD 0
    ALOAD 2
    PUTFIELD destiny/data/FlowTest$testGetAllPagesByFlow$flow$1.L$0 : Ljava/lang/Object;
    ALOAD 0
    ALOAD 3
    PUTFIELD destiny/data/FlowTest$testGetAllPagesByFlow$flow$1.L$1 : Ljava/lang/Object;
    ALOAD 0
    ICONST_1
    PUTFIELD destiny/data/FlowTest$testGetAllPagesByFlow$flow$1.label : I
    INVOKEVIRTUAL destiny/data/FlowTest$testGetAllPagesByFlow$flow$1$1.invoke (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
   L6
    DUP
    ALOAD 4
    IF_ACMPNE L7
   L8
    LINENUMBER 42 L8
    ALOAD 4
    ARETURN
   L9
    ALOAD 0
    GETFIELD destiny/data/FlowTest$testGetAllPagesByFlow$flow$1.L$1 : Ljava/lang/Object;
    CHECKCAST destiny/data/FlowTest$testGetAllPagesByFlow$flow$1$1
    ASTORE 3
    ALOAD 0
    GETFIELD destiny/data/FlowTest$testGetAllPagesByFlow$flow$1.L$0 : Ljava/lang/Object;
    CHECKCAST kotlinx/coroutines/flow/FlowCollector
    ASTORE 2
   L10
    ALOAD 1
    INVOKESTATIC kotlin/ResultKt.throwOnFailure (Ljava/lang/Object;)V
    ALOAD 1
   L7
    LINENUMBER 58 L7
    POP
   L11
   L12
    GETSTATIC kotlin/Unit.INSTANCE : Lkotlin/Unit;
    ARETURN
   L13
   L14
   L15
    NEW java/lang/IllegalStateException
    DUP
    LDC "call to 'resume' before 'invoke' with coroutine"
    INVOKESPECIAL java/lang/IllegalStateException.<init> (Ljava/lang/String;)V
    ATHROW
    RETURN
   L16
    LOCALVARIABLE $this$flow Lkotlinx/coroutines/flow/FlowCollector; L2 L14 2
    LOCALVARIABLE $fun$generate$1 Ldestiny/data/FlowTest$testGetAllPagesByFlow$flow$1$1; L4 L11 3
    LOCALVARIABLE this Ldestiny/data/FlowTest$testGetAllPagesByFlow$flow$1; L0 L13 0
    LOCALVARIABLE $result Ljava/lang/Object; L0 L13 1
    MAXSTACK = 5
    MAXLOCALS = 4
File being compiled at position: (42,46) in /destiny/data/core/src/test/java/destiny/data/FlowTest.kt
The root cause org.jetbrains.kotlin.codegen.CompilationException was thrown at: org.jetbrains.kotlin.codegen.TransformationMethodVisitor.visitEnd(TransformationMethodVisitor.kt:92)
    at org.jetbrains.kotlin.codegen.FunctionCodegen.endVisit(FunctionCodegen.java:990)
    at org.jetbrains.kotlin.codegen.FunctionCodegen.generateMethodBody(FunctionCodegen.java:487)
    at org.jetbrains.kotlin.codegen.FunctionCodegen.generateMethod(FunctionCodegen.java:260)
    at org.jetbrains.kotlin.codegen.FunctionCodegen.generateMethod(FunctionCodegen.java:176)
    ...

它很长,其余的省略。

此代码有什么问题?

如果递归方法不理想,有没有更好的解决方案? (就像generateSequence一样漂亮)。谢谢。

环境:

<kotlin.version>1.3.50</kotlin.version>
<kotlin.compiler.jvmTarget>1.8</kotlin.compiler.jvmTarget>
<dependency>
  <groupId>org.jetbrains.kotlinx</groupId>
  <artifactId>kotlinx-coroutines-core</artifactId>
  <version>1.3.2</version>
</dependency>
IntelliJ 2018.3.6

$ java -version
java version "11.0.3" 2019-04-16 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.3+12-LTS)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.3+12-LTS, mixed mode)
kotlin coroutine kotlin-coroutines flow
1个回答
2
投票

为什么要完全使用递归?以下代码将执行相同的操作

val f: Flow<Pair<String, Document?>> = flow {
    var nextUrl: String? = url

    while (nextUrl != null) {
        val doc = tools.getSuspendingDocument(nextUrl)
        emit(url to doc)

        if (doc == null) break;

        nextUrl = parser.getNextIndexPage(doc, url)

    }
}
© www.soinside.com 2019 - 2024. All rights reserved.