如何等待任务完成并返回变量?

G. *_*lly 3 android async-await kotlin kotlin-coroutines

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

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

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

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()
}
Run Code Online (Sandbox Code Playgroud)

印刷

D/BAR One
D/BAR Three
D/BAR Two
Run Code Online (Sandbox Code Playgroud)

推断的返回类型scanBarcodesTask<MutableListOf<Barcode>>

在 Dart/Flutter 中,我会编写类似的内容<T> scanBarcodes() async {}var codes = await ...解决问题。我想我可以val codes = runBlocking{...}在完成时阻止主线程。然而, Kotlin显然强烈反对这种异步模式。

Ten*_*r04 5

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

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

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.6.0"
Run Code Online (Sandbox Code Playgroud)

await()函数返回任务的结果,或者如果任务失败,它会抛出异常,因此您应该将其包装在 try/catch 中,而不是使用成功和失败侦听器。

以下是修复代码的方法:

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.
    }
}
Run Code Online (Sandbox Code Playgroud)