Как прервать длительную обработку прикосновением?

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

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

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

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

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

Этот код ниже находится внутри кнопки btRunOrStop нажатия кнопки btRunOrStop и работает нормально, с некоторыми изменениями по 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 ответ


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

В isActive , 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() для выхода из другой функции приостановки. Поэтому, если есть большой цикл for, вы можете вызвать yield() в цикле, чтобы дать ему точку выхода. Или, если есть несколько последовательных шагов, поместите вызовы 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 будет очень чистым.


Есть идеи?

10000