WorkManager:为什么使用“APPEND”ExistingWork策略的失败唯一工作不允许使用相同名称的更多工作?

11
假设我们正在开发一个消息应用程序,我们希望将消息发送到给定的对话中,其中这些消息的顺序仅在该对话中重要,如果应用程序被置于后台,则希望保证消息将被发送。 WorkManager#beginUniqueWork方法似乎非常适合此任务,其中uniqueWorkName将是某个对话ID,并且ExistingWorkPolicy.APPEND将作为工作策略以保持已安排工作的顺序。
到目前为止,在我的应用程序中,只要每个工作部件返回Result.SUCCESS,则任何未来预定的工作都将按预期执行。但是,如果一个特定的消息以致命的方式无法发送并返回Result.FAILURE,则具有相同会话ID的所有未来工作似乎都无法到达我的Worker#doWork()实现。
经过研究EnqueueRunnable类的源代码,这似乎是一个非常刻意的选择。我真正理解不了的是为什么会这样?如果uniqueWorkName失败,那么这个名称在应用程序的整个生命周期中就变得无法使用(即使杀死应用程序也是如此)。

此外,我想知道是否有任何好的解决方案,或者是否知道这在WorkManager的未来版本中会发生改变。到目前为止,我能想到的最好的方法是返回Result.SUCCESS,但将我的自定义失败状态编码到输出Data中,以使任何工作的观察者都知道它已经失败。然而这有点笨拙,并且对于代码的未来维护者不够明显(并且在查看给定的Work日志时可能会有些混淆)。

也许我的独特工作意图完全错误,还有更好的解决方案。如有任何想法,不胜感激,谢谢!


1
没有更好的了解你想要实现什么,很难回答这个问题。正如你所看到的,返回 Result.FAILURE 的效果是任何依赖于它的工作也将被标记为失败并且不会运行。这个想法是,FAILURE 表示 WorkManager 在这个链中不能再做任何事情了。另一种看待它的方式是,这个返回值用于指示 WorkManager 接下来该做什么(SUCCESS => 继续,FAILURE => 停止,RETRY => 重新运行当前的 worker)。 - pfmaggi
1
@pfmaggi 针对工作链在排队之前映射了依赖关系的情况,所有这些都是有道理的。但是这里我谈论的是uniqueWork(唯一工作),尤其是如果一个uniqueWork(唯一工作)失败,那么将来发布到相同名称的所有未来工作都不会被调度,即使在发布最新的uniqueWork(唯一工作)时,当前没有正在执行的工作。基本上一旦失败,特定的“uniqueWorkName”在应用程序的余生中就变得无法使用了,这对我来说是意外的。 - Dean
一个特别的例子是,如果我们一直尝试发送消息太久了,那么我们可能想要发送失败并向用户报告,这样他们就可以决定是否应该重试(也许消息现在已经过时)。正如我所提到的,现在我只是返回 Result.SUCCESS 并将一些失败代码放在输出 Data 中,但现在我真的不想再调用 Result.FAILURE 了,否则这个对话就变得无法使用,这对我来说似乎很奇怪! - Dean
如果您认为这是WorkManager库的问题,请在WorkManager公共问题跟踪器上打开一个错误报告或添加一个功能请求。我会尽力优先处理它。 - pfmaggi
@pfmaggi,实际上,通过问题跟踪器查看,我发现这是预期的行为,不会修复 https://issuetracker.google.com/issues/111621980。 事实上,他们甚至建议我做我现在正在做的事情,通过返回“SUCCEED”来解决问题。我想那就回答了我的问题... - Dean
显示剩余3条评论
3个回答

12

所以我在此 google 问题跟踪器报告中找到了自己问题的答案。

基本上,使用 APPEND 策略创建一个唯一的工作会产生一个 WorkContinuation,其中每个新项目都像使用 WorkContinuation#then 方法链接一样。链的失败或取消会取消所有下游工作,因此这是预期行为。

该票建议两种方法:

  

如果您真的想要 APPEND 的行为,则可以做的另一件事是检查 WorkRequests 的 WorkStatuses,并且如果(它们全部被取消),请改用 REPLACE。请注意,这本质上是有竞争性的,因为您的 WorkRequests 可能尚未被取消。因此,在使用 WorkManager 的 API 时,请确保您有一些同步原语。

  

最简单的方法是实际上不返回 Result.FAILED; 如果始终返回 SUCCEEDED 并在输出数据中返回实际的通过/失败位(如果需要),则可以确保链始终保持运行状态。

这就是我正在做的。希望这对其他人有所帮助。


在每种情况下都返回Result.success()确实解决了问题。我个人使用EventBus来发布任何先前的结果。感谢这个提示! - mrj

4

关于该问题的重要更新: https://developer.android.com/reference/kotlin/androidx/work/ExistingWorkPolicy#append_or_replace

APPEND_OR_REPLACE

枚举值 APPEND_OR_REPLACE : ExistingWorkPolicy

如果已经有一个同名的待处理工作存在,将新指定的工作附加为该工作序列的所有叶子节点的子节点。否则,将新指定的工作插入为一个新的序列的开头。

注意:如果有失败或取消的先决条件,则这些先决条件将被删除,新指定的工作将是一个新序列的开头。

这可能是团队对这个问题的响应。这个新的 ExistingWorkPolicy 在版本 2.4.0-alpha01 中可用。

在进一步测试后更新...

事实证明,它唯一可以修复的问题是如果由于某种原因无法重用 UniqueWorkContinuation ,则此操作可以修复。然而,主要特性,例如能够在许多排队了相同唯一名称的 WorkChain 中取消单个 WorkChain 仍然无法正常工作。通常,考虑以下情况:WorkChainA 包括 CompressWorkerAUploadWorkerA,然后将 WorkChainBCompressWorkerBUploadWorkerB 排队在同一个 UniqueWorkName 下。取消或失败任何一个 WorkerWorkChainA 中将导致 WorkChainB 永远不会运行...因此我们仍然必须确保每次返回 Result.success()。并且不要在 UniqueWork 内使用任何 cancelWorkByTagcancelWorkById!


1

非常感谢您对这个问题的调查。

经过一段时间的尝试,我找到了最佳解决方案,目前它对我来说是有效的。

startUpload() {

   workManager.pruneWork()
   //Prunes all eligible finished work from the internal database.

   photoAdapter.data.forEach {
      val workRequest = OneTimeWorkRequestBuilder<FileUploadWorker>()
            .setInputData(workDataOf(IMAGE_PATH_PROP to it.path))
            .addTag(UPLOADER_TAG)
            .build()

      workManager.enqueueUniqueWork(UNIQUE_WORK_TAG, ExistingWorkPolicy.APPEND, workRequest)
   }
}

调用“pruneWork”使其忘记取消或失败的工作。但是,我猜这个解决方案可能会有警告,所以要小心。

另一个想法可以看起来像这样:

workManager.getWorkInfosForUniqueWork(UNIQUE_WORK_TAG).get().forEach {

            if ( it.state == WorkInfo.State.FAILED || it.state == WorkInfo.State.CANCELLED) {
                //Redefine your UNIQUE_WORK_TAG
                UNIQUE_WORK_TAG = UUID().toString()
                return
            }
        }

尝试查找失败的WorkInfo并“切换”到新的唯一工作标签。 欢迎更好的方法。
附注:“androidx.work:work-runtime-ktx:2.3.4”除了(KEEP,REPLACE,APPEND)之外没有新的ExistingWorkPolicy。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接