如何等待任务完成和 return 变量?

How to await Task completion and return a variable?

我一直在尝试编写“惯用的”Kotlin 异步代码。我正在尝试将条形码扫描仪重构为顶级/包级功能。

如何让线程等待 scanner.process(image) 完成,并在继续之前等待 return 条形码列表?

代码部分显示了我“最接近”解决问题的尝试。

package com.example.demo

import android.util.Log
import com.google.mlkit.vision.barcode.Barcode
import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.common.InputImage
import kotlinx.coroutines.*

class BarcodeActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val scanButton = findViewById<Button>(...)
        scanButton.setOnClickListener {
          // Get Bitmap image
            runBarcode(image)
        }
    }

  // Member function, calling codes should return a list of barcodes
  // after completing 
  fun runBarcode(image: InputImage) =  runBlocking {
    Log.d("BAR", "One")
    val codes = async { scanBarcodes(image)}
    Log.d("BAR", "Three")
  }
}

// Package level function
suspend fun scanBarcodes(image: InputImage) = coroutineScope {
    val scanner = BarcodeScanning.getClient()
    val codes = async {
        scanner.process(image)
            .addOnSuccessListener { barcodes ->
                val barcodeList = mutableListOf<Barcode>()
                Log.d("BAR", "Two")
                for (barcode in barcodes) {
                    barcodeList.add(barcode)
                }
                return@addOnSuccessListener
            }
            .addOnFailureListener {
                Log.e("BAR", "Barcode scan failed")
            }
    }
    codes.await()
}

打印

D/BAR One
D/BAR Three
D/BAR Two

并且 scanBarcodes 的推断 return 类型是 Task<MutableListOf<Barcode>>

在 Dart/Flutter 中,我会写类似 <T> scanBarcodes() async {} 的东西,相应地 var codes = await ... 来解决这个问题。我想我可以在完成时使用 val codes = runBlocking{...} 来阻塞主线程。但是,这种异步模式在 Kotlin 中显然是 strongly discouraged

您可以将 runBarcode() 重写如下。

fun runBarcode(image: InputImage) {
    lifecycle.coroutineScope.launch {
        Log.d("BAR", "One")
        val codes = async { scanBarcodes(image)}
        codes.await()
        Log.d("BAR", "Three")
    }
}

await() 是可挂起的函数,它将 return DeferedJob 对象,它具有 return 来自可挂起函数 (DefferedJob())(如果有)的结果值。这将帮助您打印

D/BAR One
D/BAR Two
D/BAR Three

希望这就是您所期待的..

  1. 不要为此使用 runBlocking。它会阻塞您的主线程,从而在等待时冻结 UI(用户无法滚动、单击甚至离开您的应用程序)。它还会让您面临 ANR 崩溃的风险。您应该从您的点击侦听器中启动协程,这样您就可以将此函数设为暂停函数。

  2. 要将回调用作挂起函数,通常您必须使用 suspendCancellableCoroutine() 将其转换为挂起的东西。但是,在这种情况下,Kotlin 协程库已经为您提供了 Task.await() 挂起函数。如果无法在您的项目中导入,请将此依赖项添加到您的 build.gradle:

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.6.0"

await()函数returns任务的结果,否则任务失败会抛出异常,所以应该用try/catch包起来,而不是用success和failure听众。

修复代码的方法如下:

class BarcodeActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val scanButton = findViewById<Button>(...)
        scanButton.setOnClickListener {
            lifecycleScope.launch { 
                val barcodes = scanBarcodes(image)
                // do something with returned barcodes or maybe show message if list empty
            }
        }
    }

}

// Package level function
suspend fun scanBarcodes(image: InputImage): List<Barcode> {
    return try {
        BarcodeScanning.getClient()
            .process(image)
            .await()
    } catch(e: Exception) {
        Log.e("BAR", "Barcode scan failed")
        emptyList() // Returns an empty list on failure, but you might want to handle it differently, like returning null.
    }
}