web-dev-qa-db-fra.com

Arrêt/destruction d'un fil

J'ai un service qui lance un fil et un runnable comme ça.

t = new Thread(new Runnable() {
    public void run() {
        doSomething();
    }
});

t.start();

La raison du thread est d'effectuer une tâche asynchrone doQuelque chose () . Pour l'instant, ne nous inquiétons pas de l'autre classe AsyncTask. Je l'ai essayé et cela ne fonctionne pas pour mon cas. Edit: je ne peux pas utiliser AsyncTask car il est uniquement destiné au thread d'interface utilisateur. Ce morceau de code doit fonctionner dans un service, alors non, pas de tâche asynchrone :(

doQuelque chose () contient des bibliothèques externes, donc le problème que je rencontre est qu'il peut potentiellement être bloqué à l'une des commandes, sans renvoyer de valeur (aucune vérification d'erreur ne peut donc être effectuée).

Pour contourner ce problème, je souhaiterai, à un moment donné, détruire le Service.

stopService(new Intent("net.MyService.intent));

Cela fonctionne bien et est facilement vérifié sur le téléphone. Toutefois, le thread créé ci-dessus continuera à s'exécuter même lorsque le service qui l'a créé est détruit.

Je cherche donc les commandes correctes à insérer dans le service onDestroy () qui nettoiera le thread pour moi.

t.destroy();
t.stop();

sont tous deux amortis et provoquent des plantages d’application.

J'ai pris ce code quelque part

@Override
public void onDestroy() {

    Thread th = t;
    t = null;

    th.interrupt();

    super.onDestroy();
}

mais cela ne fonctionne toujours pas, le thread continue de s'exécuter. Toute l'aide les gars?

19
foodman

Les méthodes de thread destroy et stop sont par nature sujettes aux blocages et ne sont pas sûres. Leur existence donne également l'illusion qu'il pourrait y avoir un moyen d'arrêter immédiatement un autre fil lorsque quelque chose d'autre le dit.

Je comprends votre pensée. De votre point de vue, il s’agit d’un fil principal. Si ce fil n’a pas reçu de réponse de son fil ouvrier depuis un moment, vous souhaitez le tuer et le redémarrer, sans vous soucier de ce que c’est. Jusqu'à . Mais la raison pour laquelle ces méthodes sont déconseillées est que vous devriez faites attention à ce que le fil est en train de faire. Beaucoup.

Que se passe-t-il si le thread a un verrou autour d'une variable que vous devez utiliser plus tard? Que se passe-t-il si un thread a un handle de fichier ouvert? Dans tous ces cas, et dans bien d'autres cas, simplement arrêter le thread à son fonctionnement actuel laisserait des choses en désordre - il est fort probable que votre application se planterait juste plus tard dans la ligne.

Donc, pour qu'un thread soit interruptible, annulable ou stoppable, il doit gérer cela lui-même. Si un thread ou une opération ne fournit aucun moyen de s'interrompre, vous ne pouvez pas l'interrompre - il est supposé que cela serait dangereux.

Si vous êtes runnable est littéralement 

public void run() {
   doSomething();
}

alors il n'y a aucun moyen de l'interrompre. On pourrait espérer que si doSomething était une longue opération, il pourrait y avoir un moyen d'interagir progressivement avec quelque chose comme:

public void run() {
   while (running) {
       MyParser.parseNext();
   }
}

ou être capable de passer une variable par référence qui indique si le thread est interrompu ou non, et si tout va bien, la méthode s’interrompra à un emplacement approprié.

N'oubliez pas qu'une opération blocking est en cours de blocage. Il n’ya aucun moyen de contourner cela, vous ne pouvez pas l’annuler en cours de route.

11
Joseph Earl

Réponse alternative

Utilisez le code suivant:

MyThread thread;     // class field

Créez et démarrez le fil comme vous le faites maintenant.

thread = new MyThread();
thread.start();

Lorsque le service est détruit, "signalez" au thread de quitter

public void onDestroy() {
    // Stop the thread
    thread.abort = true;
    thread.interrupt();
}

Voici l'implémentation de thread

//another class or maybe an inner class
class MyThread extends Thread {
    syncronized boolean abort = false;

    //ugly, I know
    public void run() {
       try {
           if(!abort) doA();
           if(!abort) doB();
           if(!abort) doC();
           if(!abort) doD();
       } catch (InterruptedException ex) {
          Log.w("tag", "Interrupted!");
       }
    }
}

Vous voudrez peut-être lire ce qui suit:

Je pense que vous pouvez compter sur l’exception et ne pas vérifier l’abandon, mais j’ai décidé de le conserver.

UPDATE

J'ai vu cet exemple dans codeguru :

public class Worker implements Runnable {
    private String result;
    public run() {
        result = blockingMethodCall();
    }
    public String getResult() {
        return result;
    }
}

public class MainProgram {
    public void mainMethod() {
        ...
        Worker worker = new Worker(); 
        Thread thread = new Thread(worker); 
        thread.start();
        // Returns when finished executing, or after maximum TIME_OUT time
        thread.join(TIME_OUT); 
        if (thread.isAlive()) {
            // If the thread is still alive, it's still blocking on the methodcall, try stopping it
            thread.interrupt();
            return null;
        } else {
            // The thread is finished, get the result
            return worker.getResult(); 
        }
    }
}
2
Pedro Loureiro

Avez-vous vérifié la Obsolescence des primitives d'un thread Java Documentation référencée dans le Thread API JavaDoc. Vous trouverez des astuces pour résoudre votre problème.

1
FrVaBe

pourquoi n'utilisez-vous pas un AsyncTask ?

Une tâche peut être annulée à tout moment par En appelant cancel (booléen). L'appel de Avec cette méthode fera que les appels Suivants à isCancelled () deviendront vrais. Après l'appel de cette méthode, OnCancelled (Object), au lieu de onPostExecute (Object) sera invoqué après le retour de doInBackground (Object []) . Pour vous assurer qu'une tâche est Annulée le plus rapidement possible, vous devez toujours vérifier la valeur de retour De isCancelled () à partir de DoInBackground (Object []). ), si possible (dans une boucle par exemple.)

1
Pedro Loureiro

Mieux vaut utiliser la variable globale stopThread, arrêtez le thread dès que la variable a été remplacée par true.

btnStop.setOnClickListener(new OnClickListener() {          
        @Override
        public void onClick(View arg0){
            stopThread = true;
        }
    }); 


public void run() {
        while (!stopThread) {
           //do something
        }
}
0
ShivBuyya

Je pense que la meilleure façon de créer et de communiquer avec un autre thread est d'utiliser un AsyncTask . Voici un exemple d'un:

public class Task extends AsyncTask<Void, Void, Void> {

    private static final String TAG = "Task";

    private boolean mPaused;

    private Runnable mRunnable;

    public Task(Runnable runnable) {
        mRunnable = runnable;
        play();
    }

    @Override
    protected Void doInBackground(Void... params) {
        while (!isCancelled()) {
            if (!mPaused) {

                mRunnable.run();
                sleep();
            }
        }
        return null;
    }

    private void sleep() {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            Log.w(TAG, e.getMessage());
        }
    }

    public void play() {
        mPaused = false;
    }

    public void pause() {
        mPaused = true;
    }

    public void stop() {
        pause();
        cancel(true);
    }

    public boolean isPaused() {
        return mPaused;
    }

}

Vous pouvez maintenant utiliser facilement cette classe et démarrer le fil en écrivant:

Task task = new Task(myRunnable);
task.execute((Void) null);

Parallèlement à cela, vous pouvez facilement mettre en pause ou arrêter le fil en boucle:

Exemple de pause et de lecture du fil de discussion:

mButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if (task.isPaused()) {
            task.play();
        } else {
            task.pause();
        }
    }
});

Exemple d’arrêt et de démarrage du fil de discussion:

mButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if (task.isCancelled()) {
            task = new Task(myRunnable);
            task.execute((Void) null);
        } else {
            task.stop();
        }
    }
});
0
Anthony Cannon

J'aime adopter l'approche suivante:

class MyHandler extends Handler {
    final Semaphore stopEvent = new Semaphore(0);

    @Override
    public void handleMessage(Message msg) {
        try {
            while (!stopEvent.tryAcquire(0, TimeUnit.SECONDS)) {
                doSomething();

                if (stopEvent.tryAcquire(SLEEP_TIME, TimeUnit.MILLISECONDS)) {
                    break;
                }
            }
        } catch (InterruptedException ignored) {
        }

        stopSelf();
    }
}

Au service onDestroy vient de publier le stopEvent:

@Override
public void onDestroy() {
    myHandler.stopEvent.release();
    myHandler = null;

    super.onDestroy();
}
0
Skarllot