Получение вывода потока - PullRequest
       27

Получение вывода потока

11 голосов
/ 09 декабря 2008

Какой, по вашему мнению, лучший способ получения результатов работы потока? Представьте себе поток, который выполняет некоторые вычисления, как вы предупреждаете основную программу, в которой выполняются вычисления?

Вы могли бы опрашивать каждые X миллисекунд для некоторой общедоступной переменной, называемой «задание выполнено» или что-то еще, но тогда вы получите результаты позже, чем когда они будут доступны ... основной код будет терять время ожидания для них. С другой стороны, если вы используете более низкий X, процессор будет тратить столько времени на опрос.

Итак, что вы делаете, чтобы знать, что Нить или некоторые Нити завершили свою работу?

Извините, если он похож на этот другой вопрос , это, вероятно, причина ответа eben , я полагаю. Я имел в виду, что запускал много потоков и знал, когда все они закончили, не опрашивая их.

Я больше думал о распределении загрузки ЦП между несколькими ЦП с использованием пакетов потоков и знаю, когда пакет завершен. Я предполагаю, что это может быть сделано с Future s объектами, но этот блокирующий get метод очень похож на скрытую блокировку, а не на то, что мне нравится.

Спасибо всем за вашу поддержку. Хотя мне также понравился ответ erickson , я думаю saua самый полный, и тот, который я буду использовать в мой собственный код.

Ответы [ 7 ]

25 голосов
/ 09 декабря 2008

Не используйте низкоуровневые конструкции, такие как потоки, если вам абсолютно не нужны мощь и гибкость.

Вы можете использовать ExecutorService , например, ThreadPoolExecutor до submit () Callables . Это вернет объект Future .

Используя этот Future объект, вы можете легко проверить, сделано ли это, и получить результат (включая блокировку get(), если это еще не сделано).

Эти конструкции значительно упростят наиболее распространенные потоковые операции.

Хотелось бы уточнить про блокировку get():

Идея состоит в том, что вы хотите запустить некоторые задачи (Callable s), которые выполняют некоторую работу (вычисление, доступ к ресурсам, ...), где вам не нужен результат прямо сейчас, Вы можете просто зависеть от Executor, который будет запускать ваш код в любое время (если это ThreadPoolExecutor, то он будет запускаться всякий раз, когда будет доступен свободный поток). Тогда в какой-то момент времени вам, вероятно, понадобится результат расчета для продолжения. В этот момент вы должны позвонить get(). Если задача уже была запущена в этот момент, get() сразу же вернет значение. Если задача не была выполнена, то вызов get() будет ждать, пока задача не будет завершена. Обычно это желательно, так как вы все равно не сможете продолжить работу без результата.

Если вам не нужно значение для продолжения, но вы хотели бы узнать о нем, если оно уже доступно (возможно, показать что-то в пользовательском интерфейсе), тогда вы можете легко вызвать isDone() и только get(), если который возвращает true).

4 голосов
/ 18 декабря 2008

Вы можете создать интерфейс lister, который реализует основная программа, который вызывается работником после завершения работы.

Таким образом, вам вообще не нужно опрашивать.

Вот пример интерфейса:

/**
 * Listener interface to implement to be called when work has
 * finished.
 */
public interface WorkerListener {
    public void workDone(WorkerThread thread);
}

Вот пример реального потока, который выполняет некоторую работу и уведомляет своих слушателей:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Thread to perform work
 */
public class WorkerThread implements Runnable {
    private List listeners = new ArrayList();
    private List results;

    public void run() {
        // Do some long running work here

        try {
            // Sleep to simulate long running task
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        results = new ArrayList();
        results.add("Result 1");

        // Work done, notify listeners
        notifyListeners();
    }

    private void notifyListeners() {
        for (Iterator iter = listeners.iterator(); iter.hasNext();) {
            WorkerListener listener = (WorkerListener) iter.next();
            listener.workDone(this);
        }
    }

    public void registerWorkerListener(WorkerListener listener) {
        listeners.add(listener);
    }

    public List getResults() {
        return results;
    }
}

И, наконец, основная программа, которая запускает рабочий поток и регистрирует прослушиватель, который будет уведомлен о завершении работы:

import java.util.Iterator;
import java.util.List;

/**
 * Class to simulate a main program
 */
public class MainProg {
    public MainProg() {
        WorkerThread worker = new WorkerThread();
        // Register anonymous listener class
        worker.registerWorkerListener(new WorkerListener() {
            public void workDone(WorkerThread thread) {
                System.out.println("Work done");
                List results = thread.getResults();
                for (Iterator iter = results.iterator(); iter.hasNext();) {
                    String result = (String) iter.next();
                    System.out.println(result);
                }
            }
        });

        // Start the worker thread
        Thread thread = new Thread(worker);
        thread.start();

        System.out.println("Main program started");
    }

    public static void main(String[] args) {
        MainProg prog = new MainProg();
    }
}
1 голос
/ 09 декабря 2008

Подкласс Поток, и дать вашему классу метод, который возвращает результат. Когда метод вызывается, если результат еще не создан, тогда join () с потоком. Когда join () вернется, работа вашего Thread будет выполнена, и результат должен быть доступен; верните его.

Используйте это только в том случае, если вам действительно нужно запустить асинхронное действие, выполнить некоторую работу, пока вы ждете, а затем получить результат. В противном случае, какой смысл потока? Вы также можете просто написать класс, который выполняет работу и возвращает результат в основном потоке.

Другим подходом может быть обратный вызов: ваш конструктор должен принять аргумент, который реализует интерфейс с методом обратного вызова, который будет вызываться при вычислении результата. Это сделает работу полностью асинхронной. Но если вам вообще нужно дождаться результата в какой-то момент, я думаю, вам все равно нужно будет вызвать join () из основного потока.

1 голос
/ 09 декабря 2008

Ваш сценарий все еще немного неясен.

Если вы запускаете пакетное задание, вы можете использовать invokeAll. Это заблокирует ваш основной поток, пока все задачи не будут выполнены. При таком подходе нет «занятого ожидания», когда основной поток будет тратить ЦП на опрос isDone метода Future. Хотя этот метод возвращает список Futures, они уже «выполнены». (Существует также перегруженная версия, которая может завершиться по таймауту до завершения, что может быть безопаснее использовать с некоторыми задачами.) Это может быть намного чище, чем попытка собрать кучу Future объектов самостоятельно и попытаться проверить их состояние или заблокировать на их get методах индивидуально.

Если это интерактивное приложение, задачи которого время от времени запускаются в фоновом режиме, используйте обратный вызов , как подсказывает nick.holt , - отличный подход. Здесь вы используете submit a Runnable. Метод run вызывает обратный вызов с результатом, когда он был вычислен. При таком подходе вы можете отказаться от Future, возвращаемого submit, если только вы не хотите иметь возможность cancel запускать задачи без выключения всего ExecutorService.

Если вы хотите иметь возможность отменять задачи или использовать возможности тайм-аута, важно помнить, что задачи отменяются путем вызова interrupt в их потоке. Итак, ваша задача должна периодически проверять прерванный статус и при необходимости прерывать ее.

1 голос
/ 09 декабря 2008

В качестве альтернативы API параллелизма, описанному Saua (и если основному потоку не нужно знать, когда рабочий поток завершает работу), вы можете использовать шаблон публикации / подписки.

В этом сценарии дочернему элементу Thread / Runnable предоставляется слушатель , который знает, как обрабатывать результат и который вызывается при завершении дочернего процесса Thread / Runnable.

1 голос
/ 09 декабря 2008

Опрос a.k. занятое ожидание не очень хорошая идея. Как вы упомянули, занятое ожидание тратит впустую циклы процессора и может привести к тому, что ваше приложение перестает отвечать на запросы.

Моя Java грубая, но вы хотите что-то вроде следующего:

Если один поток должен ждать вывода другого потока, вы должны использовать условную переменную.

final Lock lock = new ReentrantLock();
final Condition cv = lock.newCondition();

Поток, заинтересованный в выводе другой угрозы, должен вызвать cv.wait(). Это приведет к блокировке текущего потока. Когда рабочий поток закончен работает , он должен вызвать cv.signal(). Это приведет к тому, что заблокированный поток станет разблокированным, что позволит ему проверить вывод рабочего потока.

0 голосов
/ 09 декабря 2008

Как отмечает saua: используйте конструкции, предлагаемые java.util.concurrent. Если вы застряли с JRE до 1.5 (или 5.0), вы, возможно, прибегнете к тому, чтобы катиться по своему усмотрению, но вам все равно лучше использовать обратный порт: http://backport -jsr166.sourceforge.net / ** 1004

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...