草庐IT

关于android:Make part of coroutine continue past cancel

codeneng 2023-03-28 原文

Make part of coroutine continue past cancellation

我有一个可以保存大文件的文件管理类。文件管理器类是一个应用程序单例,因此它比我的 UI 类寿命更长。我的 Activity/Fragment 可以从协程调用文件管理器的 save 挂起函数,然后在 UI 中显示成功或失败。例如:

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 完成,我不希望保存操作被中止。如果活动在 withContext 块开始之前被销毁,或者如果 withContext 块中有任何暂停点,则保存将不会完成,因为协程将被取消。

我想要发生的是文件总是被保存。如果 Activity 仍然存在,那么我们可以在完成时显示 UI 更新。

我认为一种方法可能是像这样从挂起函数启动一个新的 coroutineScope,但是当它的父作业被取消时,这个范围似乎仍然被取消。

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
}

  • 可能您应该在服务中进行此类工作,即使您的活动/应用程序被杀死也需要完成。因为协程就像线程一样,其生命周期取决于它运行的进程。服务使您保证您的工作将完成并且不会受到活动/应用程序被终止的影响。
  • 服务也在您的应用程序中运行,因此不能保证它不会被杀死。如果你把它设为前台服务,它就不太可能被杀死。但我从未见过任何应用程序启动前台服务只是为了保存文件。很可能,文件操作不会花费足够长的时间来承受应用程序被拆除以节省 RAM 的风险。
  • 另一种方法可能是使用不会受到应用程序终止或重启影响的 workmanager。


你可以用 NonCancellable 包裹你不想被取消的位。

1
2
3
4
5
// May cancel here.
withContext(Dispatchers.IO + NonCancellable) {
    // Will complete, even if cancelled.
}
// May cancel here.

  • 从文档看来,这应该可行。但是我开始感觉到,如果协程不能很好地取消,那是一种很大的代码气味。 NonCancellable 的文档暗示您应该很少需要它,并且只能在 finally 块中。协程忽略取消请求似乎是一种意外行为。也许 LiveData 是处理这个问题的最佳方式。
  • Kotlinconf 取消的好视频,包括 NonCancellable youtu.be/w0kfnydnFWI?t=1160
  • 如果您需要做一些原子性的事情,并且不能在中途取消,那么您就处于罕见的情况之一。暂时防止取消是可以的,只要确保你的协程是作用域的。


如果您的代码的生命周期限定为整个应用程序的生命周期,那么这是 GlobalScope 的一个用例。但是,仅仅说 GlobalScope.launch 并不是一个好的策略,因为您可能会启动多个可能发生冲突的并发文件操作(这取决于您的应用程序的详细信息)。推荐的方法是使用全局范围的 actor,作为执行器服务的角色。

基本上可以说

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"
            }
        }
    }
}

请注意,这仍然不是一个很好的解决方案,它会在其生命周期之后保留 myTextView。不过,将 UI 通知与视图分离是另一个主题。

actor 被标记为"过时的协程 API",但这只是一个预告,它将在未来的 Kotlin 版本中被更强大的替代方案所取代。这并不意味着它已损坏或不受支持。

  • 通过使用为文件管理器的生命周期创建的 CoroutineScope(它是一个为应用程序生命而存在的单例),我们会得到与您描述的相同的结果吗?关于你的例子,如果我不关心泄露 Activity 的视图,我首先不需要担心取消——我会使用生命周期范围以外的东西来启动这项工作。
  • 至于第一点,你总是可以写GlobalScope.launch(Dispatchers.IO),但这仍然会留下泄漏问题。您必须从提交的后台作业中删除对视图的所有引用,这通常意味着使用 MVVM,以便您可以引用 ViewModel。
  • @MarkoTopoInk,您有推荐的方法来处理 @ObsoleteCoroutinesApi 注释吗?我不想在我的代码中传播这个注释,而是将它限制在我直接使用演员的地方,所以有一天我可以在演员被弃用时修改 API。这样做的唯一方法是根本不使用注释并保持警告不变,还是有办法仅在此类或函数上抑制它?


我试过这个,它似乎可以按照我描述的那样做。 FileManager 类有自己的范围,但我想它也可以是 GlobalScope,因为它是一个单例类。

我们从协程在其自身范围内启动一项新工作。这是通过一个单独的函数完成的,以消除关于工作范围的任何歧义。我使用 async
为这个其他工作,所以我可以冒泡 UI 应该响应的异常。

然后在启动后,我们等待异步作业返回原始范围。 await() 挂起,直到作业完成并传递任何抛出(在我的情况下,我希望 IOExceptions 冒泡以便 UI 显示错误消息)。因此,如果原始范围被取消,它的协程永远不会等待结果,但启动的作业会继续滚动,直到它正常完成。我们要确保始终处理的任何异常都应在 async 函数中处理。否则,如果取消原??始作业,它们将不会冒泡。

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
        //...
    }
}

有关关于android:Make part of coroutine continue past cancel的更多相关文章

  1. 安卓apk修改(Android反编译apk) - 2

    最近因为项目需要,需要将Android手机系统自带的某个系统软件反编译并更改里面某个资源,并重新打包,签名生成新的自定义的apk,下面我来介绍一下我的实现过程。APK修改,分为以下几步:反编译解包,修改,重打包,修改签名等步骤。安卓apk修改准备工作1.系统配置好JavaJDK环境变量2.需要root权限的手机(针对系统自带apk,其他软件免root)3.Auto-Sign签名工具4.apktool工具安卓apk修改开始反编译本文拿Android系统里面的Settings.apk做demo,具体如何将apk获取出来在此就不过多介绍了,直接进入主题:按键win+R输入cmd,打开命令窗口,并将路

  2. ruby-on-rails - 关于 Ruby 的一般问题 - 2

    我在我的rails应用程序中安装了来自github.com的acts_as_versioned插件,但有一段代码我不完全理解,我希望有人能帮我解决这个问题class_eval我知道block内的方法(或任何它是什么)被定义为类内的实例方法,但我在插件的任何地方都找不到定义为常量的CLASS_METHODS,而且我也不确定是什么here,并且有问题的代码从lib/acts_as_versioned.rb的第199行开始。如果有人愿意告诉我这里的内幕,我将不胜感激。谢谢-C 最佳答案 这是一个异端。http://en.wikipedia

  3. ruby - 我怎样才能更好地了解/了解更多关于 Ruby 的知识? - 2

    按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭9年前。我最近开始学习Ruby,这是我的第一门编程语言。我对语法感到满意,并且我已经完成了许多只教授相同基础知识的教程。我已经写了一些小程序(包括我自己的数组排序方法,在有人告诉我谷歌“冒泡排序”之前我认为它非常聪明),但我觉得我需要尝试更大更难的东西来理解更多关于Ruby.关于如何执行此操作的任何想法?

  4. ruby - 关于 Ruby 中 Dir[] 和 File.join() 的混淆 - 2

    我在Ruby中遇到了一个关于Dir[]和File.join()的简单程序,blobs_dir='/path/to/dir'Dir[File.join(blobs_dir,"**","*")].eachdo|file|FileUtils.rm_rf(file)ifFile.symlink?(file)我有两个困惑:首先,File.join(@blobs_dir,"**","*")中的第二个和第三个参数是什么意思?其次,Dir[]在Ruby中有什么用?我只知道它等价于Dir.glob(),但是,我对Dir.glob()确实不是很清楚。 最佳答案

  5. elasticsearch源码关于TransportSearchAction【阶段三】 - 2

    1.回顾.TransportServicepublicclassTransportServiceextendsAbstractLifecycleComponentTransportService:方法:1publicfinalTextendsTransportResponse>voidsendRequest(finalTransport.Connectionconnection,finalStringaction,finalTransportRequestrequest,finalTransportRequestOptionsoptions,TransportResponseHandlerT>

  6. 关于Qt程序打包后运行库依赖的常见问题分析及解决方法 - 2

    目录一.大致如下常见问题:(1)找不到程序所依赖的Qt库version`Qt_5'notfound(requiredby(2)CouldnotLoadtheQtplatformplugin"xcb"in""eventhoughitwasfound(3)打包到在不同的linux系统下,或者打包到高版本的相同系统下,运行程序时,直接提示段错误即segmentationfault,或者Illegalinstruction(coredumped)非法指令(4)ldd应用程序或者库,查看运行所依赖的库时,直接报段错误二.问题逐个分析,得出解决方法:(1)找不到程序所依赖的Qt库version`Qt_5'

  7. ruby - 关于 Ruby/ChefSpec 编码风格的反馈 - 2

    我是Ruby的新手,但过去两周我一直在对Chef测试进行大量研究。该测试使用ChefSpec和Fauxhai,但它看起来不是很“像ruby”,我希望社区能给我一些编码风格的建议。有没有更好的方法来编写这样的嵌套循环?Recipe/foo/recipes/default.rbpackage"foo"doaction:installendRecipe/foo/spec/default_spec.rbrequire'chefspec'describe'foo::default'doplatforms={"debian"=>['6.0.5'],"ubuntu"=>['12.04','10.04

  8. ruby - 关于 ruby​​ 类变量的困惑 - 2

    假设一个使用类变量的简单ruby​​程序,classHolder@@var=99defHolder.var=(val)@@var=valenddefvar@@varendend@@var="toplevelvariable"a=Holder.newputsa.var我猜结果应该是99,但输出不是99。我想知道为什么。由于类变量的范围是类,我假设@@var="toplevelvariable"行不会影响类中的变量。 最佳答案 @@var是Holder的类变量。而顶层的@@var不是Holder的同名类变量@@var,是你在创建类Obj

  9. 一文解决关于VLAN所有的疑惑 - 2

    一文解决关于VLAN所有的疑惑VLAN基本概念为什么需要VLAN?怎么在交换机上划分VLAN,VLAN的工作原理有了子网,已经隔离了广播,还需要VLAN干啥?只进行子网划分,不进行VLAN划分VLAN划分与子网划分附加VLAN信息的方法VLAN划分交换机的端口类型(Access和Trunk)一、访问链接二、汇聚链接汇聚链接VLAN间通信为什么要进行VLAN间通信?路由器实现VLAN间通信路由器和交换机的连接方式通信细节三层交换机实现VLAN间通信加速VLAN间通信三层交换机与路由器三层交换机路由器路由器和交换机配合构建LAN的实例使用VLAN设计局域网的特点VLAN增加网络的灵活性不使用VLA

  10. ruby - 关于 CoffeeScript 变量范围的困惑 - 2

    我正在尝试了解CoffeeScript变量的范围。根据文档:ThisbehavioriseffectivelyidenticaltoRuby'sscopeforlocalvariables.但是,我发现它的工作方式不同。在CoffeeScript中a=1changeValue=->a=3changeValue()console.log"a:#{a}"#Thisdisplays3在ruby中a=1deffa=3endputsa#Thisdisplays1有人能解释一下吗? 最佳答案 Ruby的局部变量(以[a-z_]开头)arerea

随机推荐