【问题标题】:How to run on UI thread from a background thread and wait for result?如何从后台线程在 UI 线程上运行并等待结果?
【发布时间】:2020-01-08 22:25:26
【问题描述】:

我有一个第三方库,它会定期查询我的视频播放器 (ExoPlayer) 以获取视频中的当前位置等信息。这个第三方库在后台线程上运行。问题是,ExoPlayer 实例不允许被后台线程访问。

我的一个想法是在访问 ExoPlayer 实例之前使用协程强制切换到主线程。像这样的东西(注意这是从多个地方调用的,包括主线程和后台线程):

suspend fun getCurrentPosition(): Long {
    if (Looper.myLooper() != Looper.getMainLooper()) {
        // On a background thread, switch to main thread and return current position
        return withContext(Dispatchers.Main) {
            exoPlayerInstance.currentPosition
        }
    } else {
        // Already on main thread, no need to switch threads
        return exoPlayerInstance.currentPosition
    }
}

然后我会像这样使用 runBlocking 调用它:

runBlocking { videoPlayerWrapper.getCurrentPosition() }

使用runBlocking 是因为第三方库希望立即获得结果。

这有时有效,但有时我的应用程序完全锁定。知道可能出了什么问题吗?任何替代解决方案?

【问题讨论】:

    标签: android multithreading kotlin kotlin-coroutines


    【解决方案1】:

    runBlocking 是一个挂起函数,它会阻塞当前线程,直到协程启动完成。我猜你正在用这个调用阻塞主线程。我建议你使用这样的功能。

      fun getCurrentPosition() = runBlocking(Dispatchers.Main.immediate) {
          exoPlayerInstance.currentPosition
      }
    

    Dispatchers.Main.immediate 仅在当前线程不是主线程时才切换上下文。

    【讨论】:

      【解决方案2】:

      然后我会像这样使用 runBlocking 来调用它:

      runBlocking { videoPlayerWrapper.getCurrentPosition() }

      根据定义,runBlocking 将阻塞当前的Thread,直到协程完成。来自runBlockingdocs

      运行一个新的协程并中断当前线程直到 它的完成。不应在协程中使用此函数。它 旨在将常规阻塞代码桥接到以下库 以悬浮式编写,用于主要功能和 测试。

      这实际上意味着如果您从主 Thread 调用 runBlocking,您的 UI 将冻结。我的猜测是,有时videoPlayerWrapper.getCurrentPosition() 返回的速度足够快,以至于您不会注意到它实际上是阻塞的。

      此第三方库在后台线程上运行。问题是, ExoPlayer 实例不允许被后台访问 线程。

      在我看来,这里存在设计缺陷。而不是图书馆试图获得Exoplayer 实例,图书馆应该为消费者建立一个提供职位的合同。如果使用协程,库可能需要Flow<Int>,它会随着位置的变化发出当前位置。

      【讨论】:

      • 同意库缺陷,但它不在我的掌控之中。该库要求您向它传递一个接口的实现,该接口具有方法定义,例如:“fun getVideoPosition(): Long”。我仍然不明白为什么我的 UI 线程与上面的代码死锁。
      • 你从哪里打电话给runBlocking{}?来自Thread
      • 我不认为Looper 检查增加了太多价值,因为withContext(Dispatchers.Main) 确保您切换到主Thread,如果您已经在主Thread 上,则没有上下文转变。我的意思是返回withContext(Dispatchers.Main){exoPlayerInstance.currentPosition} 就足够了。至于 UI 冻结的原因,同样,runBlocking{} 旨在由主要功能或在测试期间使用,因为它阻止了调用它的 Thread
      • 改变我的答案... runBlocking 可能会从后台和主调用。我必须在早上仔细检查。至少从背景来看是肯定的。
      • 我知道 runBlocking 阻塞了当前线程。但它应该只阻塞直到工作完成。而且这里真的没有阻塞工作。它只是返回一个值。
      猜你喜欢
      • 1970-01-01
      • 2017-05-15
      • 1970-01-01
      • 1970-01-01
      • 2020-04-25
      • 2018-07-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多