Make part of coroutine continue past cancellation
我有一个可以保存大文件的文件管理类。文件管理器类是一个应用程序单例,因此它比我的 UI 类寿命更长。我的 Activity/Fragment 可以从协程调用文件管理器的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | //In MyActivity: private fun saveTheFile() = lifecycleScope.launch { try { myFileManager.saveBigFile() myTextView.text ="Successfully saved file" } catch (e: IOException) { myTextView.text ="Failed to save file" } } //In MyFileManager suspend fun saveBigFile() { //Set up the parameters //... withContext(Dispatchers.IO) { //Save the file //... } } |
这种方法的问题是,如果 Activity 完成,我不希望保存操作被中止。如果活动在
我想要发生的是文件总是被保存。如果 Activity 仍然存在,那么我们可以在完成时显示 UI 更新。
我认为一种方法可能是像这样从挂起函数启动一个新的
1 2 3 | suspend fun saveBigFile() = coroutineScope { //... } |
我认为另一种选择可能是让这个函数成为一个常规函数,在完成时更新一些 LiveData。 Activity 可以观察结果的实时数据,并且由于 LiveData 在生命周期观察者被销毁时会自动删除它们,因此 Activity 不会泄漏到 FileManager。如果可以改用上述不那么复杂的方法,我想避免这种模式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | //In MyActivity: private fun saveTheFile() { val result = myFileManager.saveBigFile() result.observe(this@MyActivity) { myTextView.text = when (it) { true ->"Successfully saved file" else ->"Failed to save file" } } } //In MyFileManager fun saveBigFile(): LiveData<Boolean> { //Set up the parameters //... val liveData = MutableLiveData<Boolean>() MainScope().launch { val success = withContext(Dispatchers.IO) { //Save the file //... } liveData.value = success } return liveData } |
你可以用
1 2 3 4 5 | // May cancel here. withContext(Dispatchers.IO + NonCancellable) { // Will complete, even if cancelled. } // May cancel here. |
如果您的代码的生命周期限定为整个应用程序的生命周期,那么这是
基本上可以说
1 2 3 4 5 6 | @ObsoleteCoroutinesApi val executor = GlobalScope.actor<() -> Unit>(Dispatchers.IO) { for (task in channel) { task() } } |
并像这样使用它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | private fun saveTheFile() = lifecycleScope.launch { executor.send { try { myFileManager.saveBigFile() withContext(Main) { myTextView.text ="Successfully saved file" } } catch (e: IOException) { withContext(Main) { myTextView.text ="Failed to save file" } } } } |
请注意,这仍然不是一个很好的解决方案,它会在其生命周期之后保留
我试过这个,它似乎可以按照我描述的那样做。 FileManager 类有自己的范围,但我想它也可以是 GlobalScope,因为它是一个单例类。
我们从协程在其自身范围内启动一项新工作。这是通过一个单独的函数完成的,以消除关于工作范围的任何歧义。我使用
为这个其他工作,所以我可以冒泡 UI 应该响应的异常。
然后在启动后,我们等待异步作业返回原始范围。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | //In MyActivity: private fun saveTheFile() = lifecycleScope.launch { try { myFileManager.saveBigFile() myTextView.text ="Successfully saved file" } catch (e: IOException) { myTextView.text ="Failed to save file" } } class MyFileManager private constructor(app: Application): CoroutineScope by MainScope() { suspend fun saveBigFile() { //Set up the parameters //... val deferred = saveBigFileAsync() deferred.await() } private fun saveBigFileAsync() = async(Dispatchers.IO) { //Save the file //... } } |