Да, вам следует избегать 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
будет очень чистым.