Объединение WorkContinuation не работает должным образом - PullRequest
3 голосов
/ 29 июня 2019

Я столкнулся с поведением WorkManager (версия 2.0.1), которое не могу понять.К сожалению, такое поведение приводит к проблемам в моем приложении.Чтобы проиллюстрировать мою проблему, я буду использовать более простой пример.

Предположим, у нас есть три Worker реализации - UniqueWorker1, UniqueWorker2 и FinishingWorker.

class UniqueWorker1(context: Context, workerParams: WorkerParameters) :
    Worker(context, workerParams) {

    override fun doWork(): Result {
        if (runAttemptCount == 0) {
            Log.d("UniqueWorker1", "First try.")
            return Result.retry()
        }
        Log.d("UniqueWorker1", "Second try")
        return Result.success()
    }
}

class UniqueWorker2(context: Context, workerParams: WorkerParameters) :
    Worker(context, workerParams) {

    override fun doWork(): Result {
        Log.d("UniqueWorker2", "doWork")
        return Result.success()
    }
}

class FinishingWorker(context: Context, workerParams: WorkerParameters) :
    Worker(context, workerParams) {
    override fun doWork(): Result {
        Log.d("FinishingWorker", "doWork")
        return Result.success()
    }
}

КакВы можете видеть, что первый работник успешен после второй попытки запуска.Другие просто регистрируют сообщение и возвращают успешный результат.

Теперь я ставлю этих рабочих в двух направлениях.Сначала я запускаю UniqueWorker1 как уникальную работу и приказываю WorkManager запустить FinishingWorker, когда UniqueWorker1 завершается успешно.

val uniqueWorker1 = OneTimeWorkRequest.Builder(UniqueWorker1::class.java).build()
val finishingWorker = OneTimeWorkRequest.Builder(FinishingWorker::class.java).build()

val uniqueWorkContinuation = WorkManager.getInstance()
    .beginUniqueWork("UniqueWorker", ExistingWorkPolicy.KEEP, uniqueWorker1)

val continuations = listOf(uniqueWorkContinuation)

WorkContinuation.combine(continuations)
    .then(finishingWorker)
    .enqueue()

Второй способ выглядит следующим образом: я объединяю уникальные работы UniqueWork1и UniqueWork2.Затем я говорю WorkManager запустить FinishingWorker, когда обе работы завершены.

val uniqueWorker1 = OneTimeWorkRequest.Builder(UniqueWorker1::class.java).build()
val uniqueWorker2 = OneTimeWorkRequest.Builder(UniqueWorker2::class.java).build()
val finishingWorker = OneTimeWorkRequest.Builder(FinishingWorker::class.java).build()

val uniqueWorkContinuation1 = WorkManager.getInstance()
    .beginUniqueWork("UniqueWorker1", ExistingWorkPolicy.KEEP, uniqueWorker1)
val uniqueWorkContinuation2 = WorkManager.getInstance()
    .beginUniqueWork("UniqueWorker2", ExistingWorkPolicy.KEEP, uniqueWorker2)

val continuations = listOf(uniqueWorkContinuation1, uniqueWorkContinuation2)

WorkContinuation.combine(continuations)
    .then(finishingWorker)
    .enqueue()

Теперь представьте себе такой случай.Я начинаю рабочих в первую очередь.UniqueWorker1 повторяется, потому что это его первая попытка запуска.У нас есть 30 секунд ожидания (со значениями по умолчанию BackoffPolicy).Прежде чем он попытается, я начинаю рабочих вторым способом.UniqueWorker1 не ставится в очередь (потому что он уже запущен), но UniqueWorker2 начинает свою работу.Теперь через 30 секунд UniqueWorker1 завершается успешно, WorkManager запускает FinishingWorker из-за комбинации первого способа работы.Проблема в том, что WorkManager не запускается FinishingWorker во второй раз.Почему он должен начать FinishingWorker во второй раз?Потому что комбинация работы вторым способом указывает начинать FinishingWorker, когда UniqueWorker1 успешно и UniqueWorker2 успешно.UniqueWorker2 удалось сразу, а UniqueWorker1 удалось через 30 с.

Вначале я думал, что когда WorkerManager видит, что когда одна из работ в комбинации работ уже поставлена ​​в очередь, она не закончится и не выиграетне выполняется запрос из then метода.Но я проверил это в более простом примере, и это сработало.

Итак, вывод описанной мной ситуации выглядит следующим образом:

// Run workers in a first way
D/UniqueWorker1: First try.
I/WM-WorkerWrapper: Worker result RETRY for Work [ id=7e2fe6b4-4c8e-42af-8a13-244c0cc30059, tags={ UniqueWorker1 } ]
// Run workers in a second way before 30s will pass
E/WM-EnqueueRunnable: Prerequisite b98a6246-28d4-4b25-ae50-ec3dda6cd3ac doesn't exist; not enqueuing
E/WM-EnqueueRunnable: Prerequisite 02d017e7-30b0-4038-9b44-a6217da3979c doesn't exist; not enqueuing
D/UniqueWorker2: doWork
I/WM-WorkerWrapper: Worker result SUCCESS for Work [ id=ce9810cd-9565-4cad-b7d1-9556a01eae67, tags={ UniqueWorker2 } ]
// 30s passed
D/UniqueWorker1: Second try
I/WM-WorkerWrapper: Worker result SUCCESS for Work [ id=7e2fe6b4-4c8e-42af-8a13-244c0cc30059, tags={ UniqueWorker1 } ]
I/WM-WorkerWrapper: Setting status to enqueued for c2ac89de-3a67-496f-93e6-037d85d11646
I/WM-WorkerWrapper: Worker result SUCCESS for Work [ id=c2ac89de-3a67-496f-93e6-037d85d11646, tags={ androidx.work.impl.workers.CombineContinuationsWorker } ]
I/WM-WorkerWrapper: Setting status to enqueued for 3287bbec-b1c4-488a-b64b-35e0e6b58137
D/FinishingWorker: doWork
I/WM-WorkerWrapper: Worker result SUCCESS for Work [ id=3287bbec-b1c4-488a-b64b-35e0e6b58137, tags={ FinishingWorker } ]

Как видите, FinishingWorker был поставлен в очередь только один раз,Извините за длинное объяснение, но этот пример показывает именно мою проблему.Это серьезная проблема для меня, потому что некоторые важные работники не поставлены в очередь.

Вопрос

Может кто-нибудь объяснить причину такого поведения?Это предполагаемое поведение WorkManager или это ошибка?

1 Ответ

4 голосов
/ 02 июля 2019

Я думаю, что существует некоторая путаница в отношении того, как вы подходите к уникальной работе, поэтому немного сложно рассуждать о том, что вы делаете.

У вас есть следующие последовательности:

  1. "UniqueWorker": UniqueWorker1 -> FinishingWorker
  2. "UniqueWorker1": UniqueWorker1
  3. "UniqueWorker2": UniqueWorker2
  4. (без имени): объединить (UniqueWorker1, UniqueWorker2) - -> FinishingWorker

Прежде всего, последовательность 1 не имеет ничего общего с остальным кодом.У него есть собственное имя;ничто в этом не имеет ничего общего с остальным.Он попытается выполнить независимо и преуспеет со второй попытки, когда вернет успех, а затем выполнит FinishingWorker.

Так что давайте перейдем к остальному.Этот код будет отображать сообщение об ошибке «Предварительное условие [что-то] не существует; не в очереди», если вы запускаете второй метод два раза подряд (даже не ставя в очередь первую последовательность).Это потому, что вы дважды пытаетесь поставить в очередь UniqueWorkers с помощью ExistingWorkPolicy.KEEP.Во второй раз политика вступит в силу и не ставит в очередь ничего нового.В этот момент, когда вы решите поставить FinishingWorker в очередь, у него не будет родителей.ТАК, что это поведение работает как задумано.

Похоже, у вас есть некоторая путаница относительно того, как уникальные произведения должны работать.Я думаю, что короткий ответ для вас заключается в том, что каждая логическая группа работ должна иметь одно и то же уникальное имя.В противном случае вы столкнетесь с такими странными проблемами.Вы можете, например, переписать второй метод, чтобы он был примерно таким:

WorkManager.getInstance(context)
  .beginUniqueWork("second_method_name", KEEP, listOf(uniqueWork1, uniqueWork2))
  .then(finishingWork)
  .enqueue()

Я бы предложил прочитать далее: https://developer.android.com/topic/libraries/architecture/workmanager/how-to/unique-work, а также документацию по API для:

public abstract WorkContinuation beginUniqueWork (String uniqueWorkName, 
                ExistingWorkPolicy existingWorkPolicy, 
                List<OneTimeWorkRequest> work)
...