day27 coroutine和任务的爱情长跑,application和workManager

之後四天,会讲讲以前面知识做基础开发时,会遇到的问题

在前面,讲到了coroutine是什麽,不妥善处理会有workleak,也介绍了他的exceptiom和取消,以及官方提供的lifecycleScope和viewModelScope

但是昨天coroutine托梦告诉我,他也想要细水长流的爱情呀,那些launch和async的任务都只是过客而已,利用完他就走了,心很累,我觉得吧,我们跟coroutine都甚麽交情了,这事肯定给她安排

那要application内还是application外,首先作为开发者,你要评估你任务的执行时间,如果你要执行的任务比application就应该选workManager

application内的长期任务

在这之前,我讲了许多coroutine的实用工具,也示范了一次性网路请情、socket连线、资料流的概述、paging3应用等等,但这些都只是为了在某个ui或某些情境的短期任务,当然并不是他不好,我也是参考了coroutine的最佳做法介绍的,只是长期任务,我们没讲到呀

那要如何为应用内的长期任务使用coroutine,其实答案已经讲出来了,就是在Application class内创建coroutine

class MyApp:Application() {

    val applicationScope = CoroutineScope(SupervisorJob())
    ...
}

注意我们并不需要对此作出取消,因为现在的应用场景是这个corouitne应该要和application存活一样久 而现在,我们可以利用这个作用域,运行比调用作用域生命周期更长的任务

实际范例

我直接跟官方blog借范例

//注入applicationScope
class Repository(
  private val externalScope: CoroutineScope,
  private val ioDispatcher: CoroutineDispatcher
) {
  suspend fun doWork() {
    withContext(ioDispatcher) {
      doSomeOtherWork()
      externalScope.launch {
        // if this can throw an exception, wrap inside try/catch
        // or rely on a CoroutineExceptionHandler installed
        // in the externalScope's CoroutineScope
        veryImportantOperation()
      }.join()
    }
  }
}
//注入applicationScope
class Repository(
  private val externalScope: CoroutineScope,
  private val ioDispatcher: CoroutineDispatcher
) {
  suspend fun doWork(): Any { // Use a specific type in Result
    withContext(ioDispatcher) {
      doSomeOtherWork()
      return externalScope.async {
        // Exceptions are exposed when calling await, they will be
        // propagated in the coroutine that called doWork. Watch
        // out! They will be ignored if the calling context cancels.
        veryImportantOperation()
      }.await()
    }
  }
}

上面的范例,即使viewModelScope已经destory了,在applicationScope的任务也会持续进行,并且doWork只会在veryImportantOperation完成後才回传

为什麽不用withCotext

这个方法是这样被提出的,很多人觉得用withContext把任务丢去externalScope就可以了,但人生就没这麽简单

class Repository(
  private val externalScope: CoroutineScope,
  private val ioDispatcher: CoroutineDispatcher
) {
  suspend fun doWork() {
    withContext(ioDispatcher) {
      doSomeOtherWork()
      withContext(externalScope.coroutineContext) {
        veryImportantOperation()
      }
    }
  }
}

这个code会遇到两个问题

  1. 如果运行dowork的coroutine在执行veryImportantOperation时被取消,那他将会持续执行,而到下一个取消
  2. ExceptionHandler会不如预期的运行,而Exception会被重新丢出
//withContext source code
return suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
    // compute new context
    val oldContext = uCont.context
    val newContext = oldContext + context
    // always check for cancellation of new context
    newContext.ensureActive()

在withContext里面,会将oldContext和newContext合并,以dispatcher而言,就是切换的动作,但如果像上面的范例呢,这个plus会把job一起换掉,然後就有问题了

为什麽不用GlobalScope呢

  1. 可配置性,自己创建的coroutine可以配置Dispatcher、Exception Handler、SupervisorJob等等前面提过的操作
  2. hardcode
  3. 测试会变麻烦

⚠️ Disclaimer
If it turns out that the CoroutineContext of your applicationScope matches the GlobalScope or ProcessLifecycleOwner.get().lifecycleScope one, you can directly assign them as follows

class MyApplication : Application() {
  val applicationScope = GlobalScope
}

博文给了免责申明,我也贴一下,如果你判断你的使用需求符合GlobalScope或ProcessLifecycleOwner,你也可以用

更多的细节请看博文

为什麽不用NonCancellable

  1. 你无法在测试中停止veryImportantOperation的执行
  2. 无限回圈的delay会无法帮助取消coroutine
  3. flow将无法从外部取消

我这篇有讲到NonCancellable

application外的长期任务 - WorkManager

另一个解法,是为了给在application外执行的任务,对workManager不熟悉的,请先看这篇,那要在workManager里使用coroutine,其实也非常简单,coroutine作为官方首选库,直接被包在一起,这边找最新版本

 val work_version = "2.6.0"
// Kotlin + coroutines
implementation("androidx.work:work-runtime-ktx:$work_version")

那他被包的有多简单,首先继承自CoroutineWorker而不是Worker,可以看到doWork已经被包成suspend function了,而suspend的写法,也跟之前的一样

class CoroutineDownloadWorker(
    context: Context,
    params: WorkerParameters
) : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result {
        withContext(Dispatchers.IO) {
            val data = downloadSynchronously("https://www.google.com")
            saveData(data)
            return Result.success()
        }
    }
}

对的,非常简单,其他的做法可以参考这篇workManager的github code sample

那coroutine的job要怎麽处理???
在workManager里面有个cancel(),可以取消或停止任务,而最棒的是,他会主动在coroutine丢出CancellationException去取消coroutine

coroutine:所以爱会消失对不对?我最後还是被取消了QQ

连结

必看

博文 coroutines-patterns-for-work-that-shouldnt-be-cancelled
WorkManager


<<:  Day27 MANO开源专案使用之OSM-建立篇

>>:  Day25 Plugin 从零开始到上架 - Android instagram APIs

[Day_15]回圈与生成式

回圈结构 - 使用for for回圈结构通常用於已知重复次数的方程序, 回圈结构中指定回圈变数的初始...

【从零开始的Swift开发心路历程-Day4】Xcode介面基础介绍

昨天我们提到红色框框里面的东西,今天就来根据他们的作用进行简单介绍吧! AppDelegate.sw...

云端架构图

哪个工程师人不想拥有一张帅气的云端架构图,本篇文章试着介绍 AWS, GCP 绘图工具及共通的特色。...

意外插曲Cortex-M55与Ethos-U55

https://www.ithome.com.tw/news/135770 文中提到 Cortex-...

DAY 9:Worker Pool Pattern,就。很。Pool。

什麽是 Worker Pool Pattern? 设定好 pool 的 goroutine 数量,预...