Как прервать длительную обработку прикосновением? - PullRequest
1 голос
/ 15 января 2020

Мне нужно было запрограммировать способ, который позволил бы пользователю прервать мою долгую обработку, если это необходимо пользователю, прикасаясь к определенной кнопке.

Поэтому мне нужно было 2 потока, один из которых выполнял мой длинный процесс, а другой ожидал взаимодействия с пользователем, если он хотел прервать

Из всего, что я прочитал, я понял, что лучшим решением для меня будет сопрограмм

Однако я никогда не использовал сопрограмм в Kotlin раньше. Так что я являюсь неспециалистом в этой функции.

Если честно, ни один из руководств в Inte rnet не был достаточно понятен для меня. С трудом я смог что-то сделать.

Этот код ниже находится внутри кнопки btRunOrStop обработка кликов, и она работает нормально, с некоторыми изменениями по сравнению со стандартом:

  if (! solved)   // Stopping the long process
    bStat.setText("")  // clearing the progress 
    solving = false
    runBlocking { jobGl.cancelAndJoin()} // Cancel and ends my long process.
  } else {    // Starting the long process
    bStat.setText("Solving ...") // static progress 
    GlobalScope.launch {          
      try {
      solving = true   // Warn that the long process is running
      jobGl  = async {
        runningProcess(param)   // Finally my rocket was launched
      } 
      retGl = jobGl.await()  // return of my process, after running OK
      solved = true          // warn that the process has ended OK.
      solving = false        // No more pending process
  // Generate an artificial button touch
      mEv = MotionEvent.obtain(x,y,MotionEvent.ACTION_UP,0F,0F,0)
  // False click to post processing after process OK.
      btRunOrStop.dispatchTouchEvent(mEv)  
     }
     finally{}  // Exception handing. It does not  work.
    }
   }
   else { code after successful execution}

Наконец мой долгий процесс:

suspend fun solveEquac(param:Double):Double {
  ..... if (! solving) return ......
  ..... if (! GlobalScope.isActive) return ...  // It does not work.
}

К сожалению, мне пришлось использовать переменную (solving) вместо isActive или блока исключений (try finally), что рекомендуется официальной Kotlin документацией, потому что это просто не работает Процесс останавливается, но только после его завершения.

Мои вопросы:

1) Возможна ли утечка памяти при использовании GlobalScope в той же активности?

2) Если да, то в каком состоянии это может произойти?

3) Если да, как я могу избежать этого?

4) Почему не работает обработка исключений (try finally) или isActive?

1 Ответ

1 голос
/ 15 января 2020

Да, вам следует избегать GlobalScope, так как это будет неуклюже отменять задания и предотвращать утечки. Это синглтон, поэтому он переживет вашу активность. Утечка происходит из-за того, что ваша launch лямбда захватывает ссылку на вашу активность, используя свойства вашей деятельности.

В качестве примечания, isActive всегда верно для вашего GlobalScope, потому что вы никогда не отменяли свой GlobalScope, только одна из его детских работ. И вы, вероятно, не захотите отменять GlobalScope, если вы использовали его для более чем одной работы одновременно.

Один из самых простых способов использования CoroutineScope - это присоединить его к своей деятельности в качестве делегата, поэтому ваша активность является отдельной областью, позволяющей вам напрямую звонить launch. Вам просто нужно помнить, чтобы отменить его в onDestroy(), чтобы задания не пережили вашу активность. (По крайней мере, ненадолго, пока вы отменяете свои задания - см. Ниже.)

class MyActivity: AppCompatActivity(), CoroutineScope by MainScope() {
    //...
    override fun onDestroy() {
        super.onDestroy()
        cancel() // cancel any jobs in this Activity's scope
    }
}

Рекомендуется всегда заставлять свои функции suspend самостоятельно выбирать правильного диспетчера для их работы. Чтобы сделать вашу функцию приостановки отменяемой, она должна быть чем-то с точками выхода, которые обычно являются вызовами функций приостановки с проверкой отмены (таких как yield() и delay() и ваши собственные функции, которые их вызывают). В качестве альтернативы вы можете проверить isActive и вернуть рано, если оно ложно, что делает вашу функцию отменяемой , но может использоваться как yield() для выхода из другой функции приостановки. Поэтому, если есть большое значение для -l oop, вы можете позвонить yield() в l oop, чтобы назначить ему точку выхода. Или, если есть несколько последовательных шагов, поместите между ними yield() вызовы. Альтернативой является продолжение проверки состояния isActive, которое является свойством контекста, так что вы можете просто напрямую ссылаться на него из своего блока withContext.

// Just an example. You'll have to come up with a way to break up your calculation.
suspend fun solveEquation(param:Double): Double = withContext(Dispatchers.Default) {
    val x = /** some calculation */
    yield()
    val y = /** some calculation with x */
    yield()
    var z = /** some calculation with y */
    for (i in 0..1000) {
        yield()
        z += /** some calculation */
    }
    z
}

Я не уверен, что Вы пытались сделать с try/finally. Вам просто нужно использовать их, как обычно, при работе с потоками. Вы можете поместить yield() внутри блока try или use и быть уверенным, что блок finally будет выполняться независимо от отмены.

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

var calcJob: Job? = null // Accessed only from main thread.

Тогда ваш прослушиватель кнопок может выглядеть примерно так:

btRunOrStop.onClickListener = {
    // Cancel job if it's running. 
    // Don't want to join it because that would block the main thread.
    calcJob?.let {
        bStat.setText("")
        it.cancel()
        calcJob = null
    } ?: run { // Create job if one didn't exist to cancel
        calcJob = launch { // Called on the Activity scope so we're in the main UI thread.
            bStat.setText("Solving ...")
            mEv = MotionEvent.obtain(x,y,MotionEvent.ACTION_UP,0F,0F,0)
            btRunOrStop.dispatchTouchEvent(mEv)
            // You might want to check this doesn't trigger the onClickListener and
            // create endless loop. (I don't know if it does.)

            val result = solveEquation(someParam) // background calculation
            // Coroutine resumes on main thread so we can freely work with "result"
            // and update UI here.

            calcJob = null // Mark job as finished.
        }
    }
}

Итак, следуя практике использования = withContext(Dispatchers.Default /*or IO*/){} для определения функций приостановки, вы может безопасно делать последовательные вещи в любом другом месте, и код в вашем блоке launch будет очень чистым.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...