1

AIM : To make a generic Thread class that is independent of the parent calling it, can be started/stopped/paused/resumed by the parent class calling it and perform user defined tasks (via runnable)

MY RESEARCH : SO_1 SO_2 SO_3 SO_4 SO_5 SomeBlog SomeBlog2 OracleBlog

Problem : from what i have understood:

  • Starting a background thread: threadObj.start() will execute statements of run() function of a class implementing Runnable Interface.

  • Stopping a background thread : threadObj.interrupt() will stop a thread from executing

  • Pausing a thread : threadObj.wait() will pause the thread,although, it requires additional synchronised lock mechanism

  • Resuming a thread :threadObj.notifyAll() will release resume the object, after handling the synchronised lock mechanism

Thus based on this, i wrote a generic Thread class that is supposed to run a user's set of tasks and play/pause/resume/stop via ui buttons, BUT ITS NOT WORKING:

Generic Thread.java


public class PausibleThread extends Thread {

    public static final String TAG ="PausibleThread>>";

    @Nullable
    PausibleRunnable runnable ;

    public PausibleThread(@Nullable Runnable target) {
        super(target);
        PausibleRunnable r = new PausibleRunnable(target);
        runnable=r;
    }

    @Override
    public synchronized void start() { super.start(); }

    public  synchronized void stopThread(){ this.interrupt(); }
    public  synchronized  void pauseThread(){ runnable.pause(); }
    public  synchronized  void resumeThread(){ runnable.resume(); }

PausibleRunnable.java:


import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

public class PausibleRunnable implements Runnable {
    private Object lockerObject;
    private boolean isPaused;
    private boolean isFinished;
    public static final String TAG="PausibleRunnable";

    @Nullable
    Runnable usrAction = null;

    public PausibleRunnable(@NonNull Runnable usrAction) {
        lockerObject = new Object();
        isPaused = false;isFinished = false;

        this.usrAction = usrAction;
    }

    public void run() {
        while (!isFinished) {

            if(isPaused) {
                runPauseLoop();
            }
            else {
                runUserAction();
                isFinished=true;
            }

        }
    }

    private void runPauseLoop() {
        synchronized (lockerObject) {
            while (isPaused) {

                try { lockerObject.wait(); }
                catch (InterruptedException e) { e.printStackTrace(); }

            }
        }
    }

    private void runUserAction() {
        if(usrAction !=null){ usrAction.run(); }
        else { Log.e(TAG, "run: userAction is NULL" ); }

    }




    public void pause() {
        synchronized (lockerObject) { isPaused = true; }
    }

    public void resume() {
        synchronized (lockerObject) {
            isPaused = false;
            lockerObject.notifyAll();
        }
    }


}

Ui creating a Pausible Thread and implementing various functions of it:

    //full class implementation at : https://paste.ubuntu.com/p/cTpW5Wt3Fy/
    int totalRunTime = 20 * 5;
    Pausible thread bgThread;

    private void initThread() {
        Runnable r = () -> {
            try {
                while (totalRunTime > 0) {
                    Thread.sleep(500);
                    totalRunTime--;
                    updateUi();

                }
            }
            catch (Exception e) { e.printStackTrace(); }
        };
        bgThread = new PausibleThread(r);

    }

    private void updateUi() {
        String data = "TotalRunTime=" + totalRunTime;
        runOnUiThread(() -> tvTerminal.setText(data));
    }

    @Override
    public void onClick(View v) {
        if (bgThread == null) {
            makeShortToast("Can't perform action, bg thread is null");
            return;
        }

        if (v.getId() == fabPause.getId()) {bgThread.pauseThread(); }
        else if (v.getId() == fabResume.getId()) { bgThread.resumeThread(); }
        else if (v.getId() == fabStop.getId()) { bgThread.stopThread(); }
        else if (v.getId() == fabStart.getId()) { bgThread.start(); }

    }

But this does not work. Why? I am taking a wild guess here, but i think the runnable is only running user's action to run a big sized loop and not repeatedly checking for play/pause. So what am i supposed to do?

ui sample image : https://i.stack.imgur.com/4RJs4.png

RG Banshi
  • 13
  • 2
  • 1
    Hey! This is too much code for me to review but I have a tipp: Try to work step by step and ask about one step at a time. First, write *working* code to start a thread. Then write *working* code to stop a thread. Then write *working* code to pause/resume a thread. Develope software iteratively. Don't develope too much at once. And don't ask about too much at once. – akuzminykh Apr 12 '20 at 12:41
  • @akuzminykh Apologies, but I thought writing complete details about what we have tried before asking is the rule here. And about the development, well, my end goal was to run a set of events on the bg thread and being manually able to control them. This was the smallest mvp i could think of, and it involved me creating a custom thread class, custom runnable and a code to run it.So here it is. I also searched a lot of answers for an exact implementation of stop/pause/resume, but everywhere it seemed to be different. So my question actually asks it, once and for all – RG Banshi Apr 12 '20 at 20:59

1 Answers1

1

You asked: "But this doesn't work. Why?"

I answer: Your solution does not work because you are always running in the loop inside runUserAction. You never break out of that loop to check if you are paused.

I'm afraid you'll have to remodel your solution to run usrAction in shorter loops, otherwise you will either lose state (assuming you interrupt that loop from outside), which will end up in undefined behavior, OR you will only break out of it when it's over, OR you'll pause your loop at states you don't really want to pause at [e.g. while making a network call -- after resumed you'll get a SocketTimeoutException].

I'd suggest you to go with the former approach as it's more elegant.

Edit:

Another possible solution: every iteration inside the usrAction check for PausableThread's state, i.e. see whether it's paused, stopped or whatever.

Try this:

PausableRunnable.java

    public synchronized boolean canContinue() throws Exception {
        synchronized (lockerObject) {
            if (isPaused) {
                lockerObject.wait();
            }
            if (isFinished) {
                return false;
            }
            return true;
        }
    }

PausableThread.java

    public boolean canContinue() throws Exception {
        return runnable.canContinue();
    }

and the Application.java

private void initThread() {
        Runnable r = () -> {
            try {
                while (totalRunTime > 0) {
                    if (bgThread.canContinue()) { // <--- !!!!!!
                        Thread.sleep(200);
                        totalRunTime--;
                        updateUi();
                    }
                }
            }
            catch (Exception e) { e.printStackTrace(); }
        };
        bgThread = new PausibleThread(r);

    }

This way you can run your application Runnable and still obey PausableThread's states at the times the runnable can tollereate. I.e. before/after transaction or other piece of calculation that is not supposed to be interrupted.

Edit 2:

feel free to lose ´synchronized´ modifier on methods like pause or resume, since you are already operating inside synchronized blocks in them.

netikras
  • 422
  • 4
  • 12