【问题标题】:How can I ensure Kotlin Coroutines are finishing in the correct order?如何确保 Kotlin Coroutines 以正确的顺序完成?
【发布时间】:2020-12-14 17:47:07
【问题描述】:

我正在尝试构建一个库,允许应用程序下载我提供的 json 文件,然后根据其内容从网络下载图像。到目前为止,我已经使用 Kotlin Coroutines 和 Ktor 实现了它,但是我遇到了一个问题,我不知道该怎么做。

这是我用来定义每个图像的数据类:

  data class ListImage(val name: String, val url: String)

用户调用init 函数下载新的 json 文件。下载该文件后,应用程序需要使用 getImages 下载文件定义的许多图像。然后使用数据类和适配器填充一个列表。

这是我用来获取文件的代码:

fun init(context: Context, url: String): Boolean {
  return runBlocking {
    return@runBlocking fetchJsonData(context, url)
  }
}

private suspend fun fetchJsonData(context: Context, url: String): Boolean {
  return runBlocking {
    val client: HttpClient(OkHttp) {
      install(JsonFeature) {}
    }
    
    val data = async {
      client.get<String>(url)
    }


    try {
      val json = data.await()
      withContext(Dispatchers.IO) {
        context
          .openFileOutput("imageFile.json", Context.MODE_PRIVATE)
          .use { it.write(json.toByteArray()) }
      }
    } catch (e: Exception) {
      return@runBlocking false
    }

  }
}

这可以工作并获取本地写入的文件。然后我必须根据文件的内容来获取图像。

suspend fun getImages(context: Context) {
  val client = HttpClient(OkHttp)

  // Gets the image list from the json file
  val imageList = getImageList(context)

  for (image in imageList) {
    val imageName = image.name
    val imageUrl = image.url
                
    runBlocking {
      client.downloadFile(context, imageName, imageUrl)
            .collect { download ->
              if (download == Downloader.Success) {
                Log.e("SDK Image Downloader", "Successfully downloaded $imageName.")
              } else {
                Log.i("SDK Image Downloader", "Failed to download $imageName.")
              }
            }
    }
  }
}

private suspend fun HttpClient
            .downloadFile(context: Context, fileName: String, url: String): Flow<Downloader> {
        return flow {
            val response = this@downloadFile.request<HttpResponse> {
                url(url)
                method = HttpMethod.Get
            }
            val data = ByteArray(response.contentLength()!!.toInt())
            var offset = 0
            do {
                val currentRead = response.content.readAvailable(data, offset, data.size)
                offset += currentRead
            } while (currentRead > 0)
            if (response.status.isSuccess()) {
                withContext(Dispatchers.IO) {
                    val dataPath =
                        "${context.filesDir.absolutePath}${File.separator}${fileName}"
                    File(dataPath).writeBytes(data)
                }
                emit(Downloader.Success)
            } else {
                emit(Downloader.Error("Error downloading image $fileName"))
            }
        }
    }

如果文件已经在设备上并且我没有尝试重新下载它,这也可以。问题是当我尝试在应用程序首次运行时按顺序获取文件和图像时。这是我尝试如何调用它的示例:

  lateinit var loaded: Deferred<Boolean>
  lateinit var imagesLoaded: Deferred<Unit>

  @InternalCoroutinesApi
  override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_main)
      setSupportActionBar(toolbar)

      val context: Context = this

      loaded = GlobalScope.async(Dispatchers.Default) {
          init(context)
      }
      GlobalScope.launch { loaded.await() }

      imagesLoaded = GlobalScope.async(Dispatchers.Default) {
          getDeviceImages(context)
      }
      GlobalScope.launch { imagesLoaded.await() }

      configureImageList(getImageList(context))
  }

  fun configureImageList(imageList: MutableList<Image>) {
        val imageListAdapter = ImageListAdapter(this, imageList) 
        with(image_list) {
            layoutManager = LinearLayoutManager(context)
            setHasFixedSize(true)
            itemAnimator = null
            adapter = imageListAdapter
        }
  }

这分崩离析。所以出现的两种情况是:

  1. 我按原样运行此代码:文件已下载,大约 75% 的图像在应用程序因java.io.IOException: unexpected end of stream on the url 而崩溃之前已下载。所以看起来图像在文件完全写入之前就开始下载了。

  2. 我在没有图像代码的情况下运行了一次应用程序。文件已下载。我注释掉文件下载代码,取消注释掉图片下载代码。图像已下载,该应用程序按我的意愿运行。这向我表明,如果第一个协程实际上在第二个协程开始之前完成,它将起作用。

我已经尽可能多地编写和重写了这段代码,但我无法让它运行而不发生文件写入和图像下载都成功完成。

试图让这些协程连续完成我做错了什么?

【问题讨论】:

    标签: kotlin kotlin-coroutines ktor


    【解决方案1】:

    我是在偶然发现这个question 之后才发现的。似乎我的HttpClient 对象正在共享连接而不是创建新连接,并且当服务器关闭连接时,它会导致运行中的操作意外结束。所以解决办法是:

    val client = HttpClient(OkHttp) {
                    defaultRequest {
                        header("Connection", "close")
                    }
                }
    

    我将它添加到每个 Ktor 客户端调用中,现在它可以按预期工作。

    【讨论】:

      猜你喜欢
      • 2021-10-13
      • 1970-01-01
      • 1970-01-01
      • 2021-10-15
      • 2017-08-25
      • 2019-10-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多