Coroutine生命周期范围和ISACTIVE不起作用?

发布于 2025-02-10 12:12:23 字数 3605 浏览 2 评论 0原文

我一直在与Coroutines合作,以为它们像Java线程一样工作,在长期运行的操作(例如编写大文件)中,Isalive boolean与检查IsInterrupt()相同。

过去,Coroutine一直按照预期的方式工作,但是在当前版本1.6.1-native-Mt中似乎不再像这样了。在下面的代码中,即使我切换到1.6.1非MultithReaded库,Isalive检查中的代码似乎也不会运行。

如果这是应该工作的方式?取消作业后,应采取什么正确的清理方式?

同样,在将Coroutine附加到示波器上时,在这种情况下,当新活动开始时,是否应该自动进行自动处理?如果没有像在Onpause()/onstop()阶段中的Java中那样手动取消手动取消,则似乎继续运行。

Kotlin Coroutine:

import android.os.Bundle
import android.util.Log
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.*

class TestActivity : AppCompatActivity() {

    private val tag = "TEST"
    lateinit var job: Job

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test)

        val button = findViewById<Button>(R.id.stopbutton)

        job = lifecycleScope.launch(Dispatchers.IO) {

            (1..120).forEach {

                // big long operation e.g. writing a large data file
                Log.d(tag, "ticking $it")

                if ( !isActive ) {
                    // this code never happens after....
                    // 1. button is clicked to cancel job
                    // 2. app is killed
                    Log.d(tag, "ticking stopped by button")
                    // clean up the partially written file
                    return@launch
                }

                delay(1000)
            }

        }

        button.setOnClickListener {
            job.cancel()
        }

    }

}

预期输出(应与Java线程代码相同):

ticking 1
ticking 2
ticking 3
ticking stopped

实际Coroutine输出:

ticking 1
ticking 2
ticking 3

以及用于处理线程中断的Java代码。

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    protected Thread t = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        t = new Thread(){

            @Override
            public void run() {

                for ( int x = 0; x < 120; x++ ) {
                    Log.d("TEST", "ticking " + x);

                    if ( interrupted() ) {
                        Log.d("TEST", "ticking stopped by button");
                        return;
                    }

                    // just for demoing
                    SystemClock.sleep(1000);

                }

            }
        };
        t.start();

        Button btn = findViewById(R.id.button);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                t.interrupt();
            }
        });
    }
}

更新1

在关注@Sergio和 @pawel的快速回复后,我删除了delay(),并将其替换为SystemClock.sleep(),就像在Java中一样,以模拟长期操作。现在,它的工作原理如预期的那样,尽管我想知道为什么这么多教程在没有突出问题的情况下使用它。

仍在尝试弄清楚如果不自动cancel()onstop()(如下所述)将coroutine附加到生命周期范围的意义是什么:

https://medium.com/androiddevelopers/cancellation-incellation-in-coroutines-coroutines-aa6b90163629

I've been working with Coroutines thinking they work like Java Threads where the isAlive boolean is the same as checking for isInterrupted() in a long running operation such as writing a large file.

In the past the Coroutine has worked as expected but in the current version 1.6.1-native-mt it doesn't seem to work like that any more. In the code below, the code inside the isAlive check doesn't seem to run even when I switch to the 1.6.1 non-multithreaded library.

If this is the way it's supposed to work? What should the correct way of performing a clean-up after a job is cancelled?

Also when attaching a Coroutine to a scope, in this case the Activity, shouldn't it be auto-cancelled when a new Activity is started? It seems to keep running if it isn't cancelled manually like in Java during the onPause()/onStop() phase.

Kotlin Coroutine:

import android.os.Bundle
import android.util.Log
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.*

class TestActivity : AppCompatActivity() {

    private val tag = "TEST"
    lateinit var job: Job

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test)

        val button = findViewById<Button>(R.id.stopbutton)

        job = lifecycleScope.launch(Dispatchers.IO) {

            (1..120).forEach {

                // big long operation e.g. writing a large data file
                Log.d(tag, "ticking $it")

                if ( !isActive ) {
                    // this code never happens after....
                    // 1. button is clicked to cancel job
                    // 2. app is killed
                    Log.d(tag, "ticking stopped by button")
                    // clean up the partially written file
                    return@launch
                }

                delay(1000)
            }

        }

        button.setOnClickListener {
            job.cancel()
        }

    }

}

Expected Output (Should be same as Java Thread code):

ticking 1
ticking 2
ticking 3
ticking stopped

Actual Coroutine Output:

ticking 1
ticking 2
ticking 3

And the Java code for handling thread interrupts.

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    protected Thread t = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        t = new Thread(){

            @Override
            public void run() {

                for ( int x = 0; x < 120; x++ ) {
                    Log.d("TEST", "ticking " + x);

                    if ( interrupted() ) {
                        Log.d("TEST", "ticking stopped by button");
                        return;
                    }

                    // just for demoing
                    SystemClock.sleep(1000);

                }

            }
        };
        t.start();

        Button btn = findViewById(R.id.button);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                t.interrupt();
            }
        });
    }
}

Update 1

After following @Sergio and @Pawel's quick replies I have removed delay() and replaced it with SystemClock.sleep() just like in Java to simulate long operations. Now it works as expected although I wonder why so many tutorials use it without highlighting the issue.

Still trying to figure out what is the point of attaching a coroutine to a lifecycle scope if it doesn't auto cancel() onstop() such as described here:

https://medium.com/androiddevelopers/cancellation-in-coroutines-aa6b90163629

Or maybe I'm missing the point?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

金橙橙 2025-02-17 12:12:23

这是因为delay()在内部还检查了取消函数,如果取消发生在其执行过程中,则通过抛出取消异常来停止Coroutine。要获得结果,您想尝试使用thread.sleep()或类似的阻止睡眠方法而不是delay> delay或尝试包装调用delay> delay> delay> delay>进入try-catch块。

It's because delay() function internally also checks for cancellations, and if the cancellation happens during its execution it stops a coroutine by throwing a cancellation exception. To get the result you want try to use Thread.sleep() or similar blocking sleeping methods instead of delay or try to wrap calling delay() into try-catch block.

浊酒尽余欢 2025-02-17 12:12:23

Coroutines中的取消确实是 ISACTIVE 是正确执行的一种方法。

但是您错过了内置的暂停功能内置的部分(在这种情况下 delay )检查并在内部投掷cancellationException,以便在取消后不会获得其他循环执行。

如果您想对取消做出反应“ rel =“ nofollow noreferrer”> positionhandler

job.invokeOnCompletion {
    when(it?.message) {
        null -> Log.d("Completed normally")
        "ButtonPressed" -> Log.d("Cancelled by button press")
        else -> Log.d("Cancelled: ${it.message}")
    }
}

并更改取消代码,因此您可以识别取消消息:

job.cancel("ButtonPressed")

Cancellation in coroutines is indeed cooperative and checking isActive is one way to do it properly.

But you've missed the part that built in suspending functions (in this case delay) check and throw a CancellationException internally so you won't get another loop execution after cancellation.

If you want to react to cancellation you can set a CompletionHandler:

job.invokeOnCompletion {
    when(it?.message) {
        null -> Log.d("Completed normally")
        "ButtonPressed" -> Log.d("Cancelled by button press")
        else -> Log.d("Cancelled: ${it.message}")
    }
}

And alter your cancellation code so you can discern the cancellation message:

job.cancel("ButtonPressed")
明媚殇 2025-02-17 12:12:23

如果取消Coroutine,则delay()或实际上任何其他暂停调用都可以扔concellationException。因此,一种方法是简单地使用try-catch-catch-catch在出口上清理,因为这些方法仍在运行。

如果您需要在清理过程中调用暂停功能,则需要使用withContext(noncancellable){...},以便内部的悬浮函数不会立即投掷concellationException再次。

  try {
     (1..120).forEach {
        // big long operation e.g. writing a large data file
        Log.d(tag, "ticking $it")
        delay(1000)
     }
  } catch (ex: CancellationException) {
    withContext(NonCancellable) {
       Log.d(tag, "ticking cancelled")
       // clean up interrupted file generation
    }
    throw ex
  }

If the coroutine is cancelled, then delay() or practically any other suspend call may throw CancellationException. So one approach is to simply use try-catch-finally to clean up on exit, as those are still run.

If you need to call suspend functions during cleanup, you need to use withContext(NonCancellable) { ... } so that the suspend functions inside that don't immediately throw CancellationException again.

  try {
     (1..120).forEach {
        // big long operation e.g. writing a large data file
        Log.d(tag, "ticking $it")
        delay(1000)
     }
  } catch (ex: CancellationException) {
    withContext(NonCancellable) {
       Log.d(tag, "ticking cancelled")
       // clean up interrupted file generation
    }
    throw ex
  }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文