Führen Sie verschiedene Methoden in Hintergrundthreads ohne Duplizierung aus

93123
Bobrovsky

In meiner Android-App gibt es eine Reihe von Methoden, die in einem Hintergrundthread ausgeführt werden sollten.

Eine der Methoden sieht so aus

public static void processItem(final Context context, final String itemId)
{
    if (Looper.myLooper() == Looper.getMainLooper())
    {
        Thread task = new Thread()
        {
            @Override
            public void run()
            {
                processItem(context, playlistId);
            }
        };

        task.start();
        return;
    }

    // actual processing
}

Wie Sie sehen, gibt es Code, der nur sicherstellt, dass ein in Frage kommender Thread verwendet wird.

Wie kann ich vermeiden, diesen "Threading" -Code immer wieder für jede Methode zu kopieren, die in einem Hintergrundthread ausgeführt werden soll?

Antworten
13
Sie könnten so etwas wie die `GuiUtils`-Klasse aus dieser Frage verwenden: http://codereview.stackexchange.com/questions/52197/console-component-in-javafx (es ist für javafx, aber es gilt das gleiche Prinzip) Marc-Andre vor 5 Jahren 0

3 Antworten auf die Frage

18
rolfl

Thread erweitern ist ein "Anti-Pattern" in Java. Der richtige Weg für diese Arbeit ist das Erstellen einer Instanz eines ausführbaren Objekts und das Verwenden des ausführbaren Objekts als Konstruktor für eine Thread-Instanz.

Thread t = new Thread(new Runnable() {
    public void run() {
        /*
         * Do something
         */
    }
});

t.start();

Aber, und dies ist ein großes "aber", in Java im Allgemeinen wird die Praxis, Threads wie diese zu erstellen und zu verwenden, durch Frameworks ersetzt, die die Threads besser verwalten. Im normalen Java sollten Sie die Klasse Executor, ExecutorService und Executors im Concurrent-Paket verwenden.

Bei Android-spezifischen Anwendungsfällen sollten Sie sich mit den Threading-Modellen in Android vertraut machen, wobei der AsyncTask im Android-Entwicklungskit besondere Aufmerksamkeit gilt

Update: mehr über das Thread-Anti-Pattern

Jon Skeet sagt das so ... ;-) und die meisten Leute stimmen zu. Was Sie haben, ist etwas, das getan werden muss. Das ist, was Sie implementieren müssen, die Art und Weise, wie Sie es tun. Der Ort, an dem etwas ausgeführt wird, befindet sich in einem Thread. Sie implementieren den Thread nicht, Sie implementieren die Task. Hier gibt es zwei verschiedene Konzepte: Was muss getan werden und wo muss es getan werden? Sie implementieren nur eine davon.

Update: alternative Lösung

Um die für die Verwaltung des Threads erforderliche Logik zu reduzieren, würde der Code ausgeführt werden.

  1. Verwenden Sie einfach immer einen anderen Thread. Entfernen Sie einfach die if-Bedingung und führen Sie die gesamte Verarbeitung in einer Run-Run-Methode durch.
  2. Verwenden Sie eine allgemeine Dienstmethode, die ungefähr so ​​aussieht:

    private static final ExecutorService THREADPOOL = Executors.cachedThreadPool();
    
    public static void runButNotOn(Runnable toRun, Thread notOn) {
        if (Thread.currentThread() == notOn) {
            THREADPOOL.submit(toRun);
        } else {
            toRun.run();
        }
    }
    

    und dann können Sie es in Ihrer Methode aufrufen mit:

    private static void processImplementation(final Context context, final String itemId) {
         // ... the actual work.
    }
    
    public static void processItem(final Context context, final String itemId) {
        Runnable task = new Runnable(){
            public void run() {
                processImplementation(context, itemId);
            }
        };
        runButNotOn(task, Looper.getMainLooper().getThread());
    }
    
AsyncTask ist ein guter Weg, um hierher zu gelangen! ExecutorServices sind auch in Android verfügbar und können auch verwendet werden, sofern die UI-spezifischen Anrufe in einem Aufruf von "post" / "runLater" eingeschlossen sind. Simon Forsberg vor 5 Jahren 0
Vielen Dank, dass Sie auf ein Anti-Pattern in meinem Code hingewiesen haben. Es wäre großartig, einen Link zu Erklärungen dafür zu haben, warum dies ein Anti-Muster ist. Übrigens, ich sehe nicht, wie Ihre Antwort die Duplizierung des "Threading" -Codes anspricht. Bobrovsky vor 5 Jahren 0
@Bobrovsky - Die Antwort wurde mit einigen Details aktualisiert rolfl vor 5 Jahren 0
Danke für die tolle Antwort. Es sieht so aus, als wäre die Verwendung eines gemeinsam genutzten `ExecutorService` und Runnables am nächsten, was mir gefällt :-) Bobrovsky vor 5 Jahren 0
@SimonForsberg - Angesichts der Tatsache, dass OP fragt, wie die Duplizierung von Code minimiert werden soll, um eine Funktion in einem Hintergrund-Thread auszuführen, geht AsyncTask nicht in die entgegengesetzte Richtung **? Das ist noch komplexer als der ursprüngliche Code von OP. Vielleicht jetzt einfacher mit Lambdas und anonymen Funktionen, aber ich sehe nicht, was AsyncTask hinzufügt? ToolmakerSteve vor 4 Jahren 0
@ToolmakerSteve Lambdas sind auf Android nicht leicht verfügbar, da Android Java 8 nicht vollständig unterstützt. [`AsyncTask`] (http://developer.android.com/reference/android/os/AsyncTask.html) ist eine sehr verbreitete und empfohlene Methode, um Vorgänge im Hintergrund auszuführen. Dies führt nicht zu mehr dupliziertem Code als bei der Verwendung von `ExecutorService`s. Es ist nur eine andere Art, die Aufgaben zu starten. Simon Forsberg vor 4 Jahren 0
4
Marco Acierno

Sie können ein Runnable an die processItemFunktion übergeben, um sicherzustellen, dass der Looper mit dem Haupt-Looper identisch ist. Wenn ja, starten Sie einen Thread mit dem an ihn übergebenen Callback.

processItem(Runnable callback);

wird sein:

public static void processItem(Runnable callback)
{
    if (Looper.myLooper() == Looper.getMainLooper())
    {
        new Thread(callback).start();
    }
}

Und wenn Sie es anrufen:

processItem(new Runnable() {
  @Override
  public void run() {
    What you want to do?
  }
});

PS Ohne mehr Kontext / Informationen können wir Ihnen nicht wirklich helfen ... Und ich denke, es ist eine Frage für Stackoverflow, aber ich bin mir nicht sicher.

Ihre Antwort scheint unvollständig zu sein. Sie haben den benutzerdefinierten Code in einem benutzerdefinierten `run` verschoben, aber Sie zeigen nicht, wie` run` aufgerufen wird, wenn myLooper NICHT mainLooper ist. ToolmakerSteve vor 4 Jahren 0
2
nhaarman

Treten Sie einen Schritt zurück und betrachten Sie Ihre Entwurfsentscheidungen. Sie sprechen von 'Ausführen einer Methode in einem Hintergrundthread'. Was ich vorschlagen möchte, ist, über die Aufgabe nachzudenken, die Sie zu erreichen versuchen, und daraus die Logik zu erstellen.

Beispielsweise kann Ihre Methode processItemzu einer Klasse gehören, die für Sie das Threading übernimmt :

/**
 * A class which processes item on a separate thread.
 */
public class ItemProcessor {

    private final Context mContext;
    private final String mItemId;

    public ItemProcessor(final Context context, final String itemId){
        mContext = context.getApplicationContext();
        mItemId = itemId;
    }

    /**
     * Starts processing the item on a separate thread.
     */
    public void process() {
        new Thread(new Runnable() {

            @Override
            public void run() {
                // Do the processing.
            }
        }).start();
    }

}

Denken Sie über Module in Ihrer Anwendung nach, schreiben Sie nicht einfach linearen Code. Zum Beispiel ist es jetzt viel einfacher, der Klasse eine Callback-Schnittstelle hinzuzufügen, indem eine Setter-Methode verwendet wird.