removeCallbacks not stopping runnable
Asked Answered
D

5

40

I am calling from a method:

myHandler.postDelayed(mMyRunnableHide, 6000);

which calls:

public Runnable mMyRunnableHide = new Runnable()
{

    public void run()
    {
        mTextDisplay.setText("");
        DisplayX();
    }
 };

if a button on screen is clicked I want to stop the runnable:

   Button next = (Button) findViewById(R.id.Breaction);
    next.setOnClickListener(new View.OnClickListener() {
        public void onClick(View view) {

            myHandler.removeCallbacks(mMyRunnableHide);

            mTextDisplay.setText("");
            DisplayX();
            }
        });   
    }

the removecallbacks is not stopping the runnable. What am I doing wrong? Am I using the correct method? I just want the runnable to "Not Run" when the user clicks the button.

Thanks for any help.

Dormant answered 30/4, 2011 at 20:3 Comment(1)
@YekmerSimsek Runnable class has no stop() method.Struthious
P
37

It appears to me that removeCallbacks(..) only stops pending messages (Runnables). If your runnable has already started, then there's no stopping it (at least not this way).

Alternatively, you can extend the Runnable class and give it some kind of kill switch like this:

public class MyRunnable implements Runnable
{
   private boolean killMe = false;

   private void run()
   {
      if(killMe)
         return;

      /* do your work */
   }

   private void killRunnable()
   {
      killMe = true;
   }
}

This will only prevent it from starting, but you could occasionally check killMe and bail out. If you are looping the runnable (like some kind of background thread) you can say:

while(!killMe) {
   /* do work */
}

Hope this helps

EDIT I just wanted to post an update on this. Since this original post, Google has come up with a great class called AsyncTask that handles all of this stuff for you. Anyone reading this really should look into it because it is the correct way of doing things.

You can read about it here

Pug answered 30/4, 2011 at 20:29 Comment(12)
I agree sometimes the computer is trying to kill me :-) Thanks thoDormant
Be sure to mark killMe as volatile. Otherwise, the compiler is likely to replace while(!killMe) with while(true), since it never changes (as far as it can tell).Iodic
I've never had that problem with this method (i've used similar methods quite a lot), but i'll keep a look out for that.Pug
typo, MyRunnable "implements" Runnable {...} not "extends"Monometallism
@AndyShulman volatile variables are most useful when you have many threads accessing the same global variable. What volatile is actually saying is, "Never store this variable in a register. Always read it from memory". This becomes important if you have code where a variable is frequently read/changed and must remain accurate across threads.Pug
The fact that his code is implementing Runnable is a good clue that he's probably using multiple threads.Iodic
Yes, but it doesn't have to. I wasn't saying you were wrong, I was merely adding information to the thread for anyone who might need to know.Pug
@AndyShulman you're wrong. Neither javac nor the java/dalvik vm will do what you claim it will.Carlyle
@Pug removeCallbacks is synchronous and takes effect immediately when called from the main thread. There ican be no "already started" callback running concurrently in the main thread. If a Runnable were running then the button handler could never run until the Runnable finishes. If everything is called from the main thread flags are not needed.Hemmer
This solution is wrong. The killMe variable should be marked as volatile, as @AndyShulman says. mtmurdock, I encourage you to look at the Java Memory Model specification or, if that's too dense, you can just look at many posts out there that explains the subtleties of this. The explanation you give about having "many threads" is just wrong, since this problem can manifest as soon as you have more than one thread running in your app. Also, in Java, volatile is not necessarily tied to "register" of the machine.Ar
And for the rest of the run of the program it will come here all the time and check that condition, if it can come here more than once it can cause a serious overhead. Why does those hacks get so many upvotes? Java is seriously terrible.Plosive
I think the good answer is the one of Mister Smith, you should change itIceland
H
15

Handler.removeCallback is synchronous and will work nicely provided:

  1. You call postDelayed always in the main thread.
  2. You call removeCallback always in the main thread
  3. You don't call postDelayed again after having removed callbacks.

So in your case removeCallbacks is called from a button handler, which runs in the main thread. But you didn't show in your code the point from where you call postDelayed. If you call it from a background thread thats where your problem is.

If you are sure you don't call any of these methods from background threads, and the order of the calls is correct, then you might be leaving uncancelled tasks unadvertedly alive due to activity recreation on config changes (screen rotation, etc). Always make sure to call removeCallbacks again in the onDestroy method to prevent this kind of problems.

Hemmer answered 15/3, 2016 at 16:4 Comment(0)
H
7

Here is another way to accomplish what mtmurdock is describing. This class will allow editing of instance variables in any class that your Runnable is defined as an anonymous inner class.

package support;

/**
* Runnable that can be stopped from executing
*/
public abstract class KillableRunnable implements Runnable{

private boolean isKilled=false;

/**
 * Instead of Overriding run(), override this method to perform a Runnable operation. 
 * This will allow editing instance variables in the class that this Runnable is defined
 */
public abstract void doWork();

//The handler that posts this Runnable will call this method. 
//By default, check if it has been killed. doWork() will now be the method 
//override to implement this Runnable
@Override
final public void run(){
    if(!isKilled){
        doWork();
    }
}

final public void kill(){
    isKilled=true;
}
}
Haply answered 20/2, 2015 at 17:54 Comment(1)
Hmm.. And what happens when doWork() is already running, and you call kill() from other thread? :-)Janaye
S
0

I don't think that removeCallbacks(..) only stops pending messages (Runnables) ,I think removeCallbacks(..) not working have other cause,but i don‘t know. because postDelayed(..) and removeCallbacks(..) is in the same thread

Sapper answered 29/6, 2018 at 9:40 Comment(0)
B
0

the following has worked for me. Place it in onResume.

mService= null;

public void onServiceConnected(ComponentName name, IBinder service) {
        Log.i(TAG, "OnServiceConnected");
        ContadorFG.LocalBinder binder = (ContadorFG.LocalBinder) service;
        mService = binder.getService();
        connected = true;
        synchronized (lock){
            lock.notifyAll();
        }
    }

public void onResume() {
    super.onResume();
    loopDelayed();
}  

private void loopDelayed(){ 
    final Handler handler = new Handler();
    handler.postDelayed(new Runnable() {
        @Override
        public void run() {
            if (mService != null) {

                ----
                ----
                ----

                return;
            }else{
                //auto call 
                loopDelayed();
            }
        }
    }, 10);

}
Beryllium answered 18/4, 2020 at 19:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.