WorkManager:为什么失败的唯一工作与 "APPEND" ExistingWork 策略不允许更多同名工作?
WorkManager: Why does failed unique work with the "APPEND" ExistingWork strategy not allow more work under the same name?
假设我们正在开发一个消息传递应用程序,我们希望将消息发送到给定的对话中,其中这些消息的顺序仅在该对话中很重要,如果该应用程序被置于后台,我们希望保证消息将被发送。
WorkManager#beginUniqueWork
方法似乎很适合这个,其中 uniqueWorkName
将是一些对话 ID,ExistingWorkPolicy.APPEND
将用作工作策略以保持工作顺序预定。
到目前为止,在我的应用程序中,只要每个工作 returns Result.SUCCESS
,那么任何未来计划的工作都会按预期执行。然而,如果一个特定的消息无法以致命的方式发送并且我 return Result.FAILURE
,那么所有未来使用相同对话 ID 的工作似乎永远不会达到我的 Worker#doWork()
实现。
在深入研究 EnqueueRunnable
class 的源代码后,这似乎是一个非常慎重的选择。我无法真正理解的是为什么会这样?奇怪的是,如果 uniqueWorkName
失败,该名称将在应用程序的剩余生命周期内变得不可用(这在终止应用程序后仍然存在)。
此外,我想知道是否有人对此有好的解决方案,或者知道这是否会在 WorkManager
的未来版本中改变。到目前为止,我能想到的最好的事情是 return Result.SUCCESS
但在输出 Data
中编码我自己的失败状态,以便任何观察者都知道它失败了。然而,这有点尴尬并且对于代码的未来维护者来说不是很明显(并且在查看 Work
的给定片段的日志时可能会有点混乱)。
也许我对独特作品的预期用途是完全错误的,并且有更好的解决方案。任何想法将不胜感激,谢谢!
所以我在 this google issue tracker report 中找到了我自己的问题的答案。
基本上,使用 APPEND
策略的独特工作会创建一个 WorkContinuation
,其中每个新项目都被链接起来,就好像我们要使用 WorkContinuation#then
方法一样。失败或取消链会取消所有下游工作,因此这是预期的行为。
工单建议 2 种方法:
If you really want APPEND's behavior, then another thing you could do is to check for WorkStatuses of the WorkRequests, and if (all of them happened to be cancelled) use REPLACE instead of APPEND. Bear in mind, this is inherently racy, because your WorkRequests might not have cancelled yet. So make sure you have some synchronization primitives around your use of WorkManager's APIs.
和
The simplest way to do this is to not actually return Result.FAILED; if you always return SUCCEEDED and return the actual pass/fail bit (if needed) in the output data, you can make sure the chain always keeps running.
这就是我已经在做的事情。希望这对其他人有帮助。
关于此事的重要更新:
https://developer.android.com/reference/kotlin/androidx/work/ExistingWorkPolicy#append_or_replace
APPEND_OR_REPLACE
enum val APPEND_OR_REPLACE : ExistingWorkPolicy
If
there is existing pending (uncompleted) work with the same unique
name, append the newly-specified work as the child of all the leaves
of that work sequence. Otherwise, insert the newly-specified work as
the start of a new sequence.
Note: If there are failed or cancelled
prerequisites, these prerequisites are dropped and the newly-specified
work is the start of a new sequence.
这可能正是团队对这个问题的回应。这个新的 ExistingWorkPolicy
在版本 2.4.0-alpha01
中可用。
进一步测试后编辑...
所以事实证明,此修复的唯一问题是无法重用 UniqueWorkContinuation
如果由于某种原因它失败了一次。然而,主要特征,例如当许多 Workchains
是具有相同唯一名称的队列时,能够取消单个 WorkChain
仍然不起作用。通常,考虑:WorkChainA
与 CompressWorkerA
和 UploadWorkerA
,然后排队 WorkChainB
与 CompressWorkerB
和 UploadWorkerB
,都在同一个 UniqueWorkName
.在 WorkChainA
中取消或失败任何 Worker
将导致 WorkChainB
永远不会 运行... 因此我们仍然必须确保 return Result.success()
每次。并且不要在 UniqueWork
!
中使用任何 cancelWorkByTag
或 cancelWorkById
堆叠在一起,感谢您对这个问题的调查。
经过一段时间寻找最佳解决方案后,目前这个对我有用
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,并 "switch" 找到新的唯一工作标签。
更好的方法表示赞赏。
p.s。 "androidx.work:work-runtime-ktx:2.3.4" 除了 (KEEP,REPLACE,APPEND)
没有新的 ExistingWorkPolicy
假设我们正在开发一个消息传递应用程序,我们希望将消息发送到给定的对话中,其中这些消息的顺序仅在该对话中很重要,如果该应用程序被置于后台,我们希望保证消息将被发送。
WorkManager#beginUniqueWork
方法似乎很适合这个,其中 uniqueWorkName
将是一些对话 ID,ExistingWorkPolicy.APPEND
将用作工作策略以保持工作顺序预定。
到目前为止,在我的应用程序中,只要每个工作 returns Result.SUCCESS
,那么任何未来计划的工作都会按预期执行。然而,如果一个特定的消息无法以致命的方式发送并且我 return Result.FAILURE
,那么所有未来使用相同对话 ID 的工作似乎永远不会达到我的 Worker#doWork()
实现。
在深入研究 EnqueueRunnable
class 的源代码后,这似乎是一个非常慎重的选择。我无法真正理解的是为什么会这样?奇怪的是,如果 uniqueWorkName
失败,该名称将在应用程序的剩余生命周期内变得不可用(这在终止应用程序后仍然存在)。
此外,我想知道是否有人对此有好的解决方案,或者知道这是否会在 WorkManager
的未来版本中改变。到目前为止,我能想到的最好的事情是 return Result.SUCCESS
但在输出 Data
中编码我自己的失败状态,以便任何观察者都知道它失败了。然而,这有点尴尬并且对于代码的未来维护者来说不是很明显(并且在查看 Work
的给定片段的日志时可能会有点混乱)。
也许我对独特作品的预期用途是完全错误的,并且有更好的解决方案。任何想法将不胜感激,谢谢!
所以我在 this google issue tracker report 中找到了我自己的问题的答案。
基本上,使用 APPEND
策略的独特工作会创建一个 WorkContinuation
,其中每个新项目都被链接起来,就好像我们要使用 WorkContinuation#then
方法一样。失败或取消链会取消所有下游工作,因此这是预期的行为。
工单建议 2 种方法:
If you really want APPEND's behavior, then another thing you could do is to check for WorkStatuses of the WorkRequests, and if (all of them happened to be cancelled) use REPLACE instead of APPEND. Bear in mind, this is inherently racy, because your WorkRequests might not have cancelled yet. So make sure you have some synchronization primitives around your use of WorkManager's APIs.
和
The simplest way to do this is to not actually return Result.FAILED; if you always return SUCCEEDED and return the actual pass/fail bit (if needed) in the output data, you can make sure the chain always keeps running.
这就是我已经在做的事情。希望这对其他人有帮助。
关于此事的重要更新: https://developer.android.com/reference/kotlin/androidx/work/ExistingWorkPolicy#append_or_replace
APPEND_OR_REPLACE
enum val APPEND_OR_REPLACE : ExistingWorkPolicy
If there is existing pending (uncompleted) work with the same unique name, append the newly-specified work as the child of all the leaves of that work sequence. Otherwise, insert the newly-specified work as the start of a new sequence.
Note: If there are failed or cancelled prerequisites, these prerequisites are dropped and the newly-specified work is the start of a new sequence.
这可能正是团队对这个问题的回应。这个新的 ExistingWorkPolicy
在版本 2.4.0-alpha01
中可用。
进一步测试后编辑...
所以事实证明,此修复的唯一问题是无法重用 UniqueWorkContinuation
如果由于某种原因它失败了一次。然而,主要特征,例如当许多 Workchains
是具有相同唯一名称的队列时,能够取消单个 WorkChain
仍然不起作用。通常,考虑:WorkChainA
与 CompressWorkerA
和 UploadWorkerA
,然后排队 WorkChainB
与 CompressWorkerB
和 UploadWorkerB
,都在同一个 UniqueWorkName
.在 WorkChainA
中取消或失败任何 Worker
将导致 WorkChainB
永远不会 运行... 因此我们仍然必须确保 return Result.success()
每次。并且不要在 UniqueWork
!
cancelWorkByTag
或 cancelWorkById
堆叠在一起,感谢您对这个问题的调查。
经过一段时间寻找最佳解决方案后,目前这个对我有用
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,并 "switch" 找到新的唯一工作标签。 更好的方法表示赞赏。
p.s。 "androidx.work:work-runtime-ktx:2.3.4" 除了 (KEEP,REPLACE,APPEND)
没有新的 ExistingWorkPolicy