Как смоделировать буферизованное периферийное устройство с помощью SwingWorker?

Я использую это упражнение в качестве педагогического инструмента, чтобы помочь мне усвоить некоторые концепции программирования Java GUI. Я ищу общее понимание, а не подробное решение одной конкретной проблемы. Я ожидаю, что такое «правильное» кодирование многому научит меня, как подходить к будущим многопоточным проблемам. Если это слишком общее для этого форума, возможно, это относится к Программистам?

Я моделирую кардридер. У него есть графический интерфейс, позволяющий загружать карты в загрузочную воронку и нажимать «Старт» и т. Д., Но его основным «клиентом» является ЦП, работающий в отдельном потоке и запрашивающий карты.

Устройство чтения карт поддерживает единственный буфер. Если поступает запрос карты и буфер пуст, устройство чтения карт должно считать карту из бункера (что занимает 1/4 секунды, это 1962 год). После того, как карта была прочитана в буфер, устройство чтения карт отправляет буфер в ЦП и немедленно инициирует другую операцию загрузки буфера перед следующим запросом.

Если не только буфер пуст, но и нет карты в бункер, то мы должны дождаться, пока оператор поместит колоду в бункер и не нажмет Старт (что всегда инициирует операцию загрузки буфера).

В моей реализации запросы карты отправляются в устройство чтения карт в форма invokeLater () Runnables в очереди на EDT. Во время myRunnable.run () либо будет доступен буфер (в этом случае мы можем отправить его в ЦП и запустить другую операцию загрузки буфера), либо буфер будет пуст. Что, если он пуст?

Две возможности: (а) уже выполняется операция загрузки буфера или (б) лоток для карт пуст (или еще не запущен). В любом случае недопустимо заставлять EDT ждать. Работа (и ожидание) должна выполняться в фоновом потоке.

Для простоты я попытался создать SwingWorker в ответ на каждый запрос карты, независимо от состояния буфера. Псевдокодом был:

SwingWorker worker = new SwingWorker() {
    public Void doInBackground() throws Exception {
        if (buffer.isEmpty()) {
            /*
             * fill() takes 1/4 second (simulated by Thread.sleep)
             * or possibly minutes if we need to have another 
             * card deck mounted by operator.
             */
            buffer.fill();
        }
        Card card = buffer.get(); // empties buffer
        /*
         * Send card to CPU
         */
        CPU.sendMessage(card); // <== (A) put card in msg queue
        /* 
         * Possible race window here!!
         */
        buffer.fill(); //         <== (B) pre-fetch next card
        return null;
    }
};
worker.execute();

Это произвело некоторые странные эффекты синхронизации - я подозреваю, из-за гонки buffer.fill () , которая могла произойти следующим образом: если между (A) и (B), ЦП получил карту, отправил запрос на другую и от своего имени создал еще один поток SwingWorker, тогда могут быть два потока одновременно, пытающиеся заполнить буфер. [Удаление вызова предварительной выборки в (B) решило эту проблему.]

Поэтому я считаю, что создание потока SwingWorker для каждого чтения неверно. Буферизация и отправка карт должны быть сериализованы в одном потоке. Этот поток должен попытаться выполнить предварительную выборку буфера и должен иметь возможность ждать и возобновлять работу, если у нас заканчиваются карты и мы должны ждать, пока в загрузочный лоток будут помещены новые. Я подозреваю, что SwingWorker имеет то, что требуется для того, чтобы быть долго работающим фоновым потоком, чтобы справиться с этим, но я еще не совсем там.

Предполагая, что поток SwingWorker - это путь, как я могу реализовать это, устраняя задержку на EDT, позволяя потоку блокироваться в ожидании пополнения бункера и обрабатывать неопределенность того, завершится ли заполнение буфера до или после получения другого запроса карты?


РЕДАКТИРОВАТЬ: Я получил ответ от другого потока и резюмирую здесь:

Вместо использования потока SwingWorker было рекомендовано создать ExecutorService newSingleThreadExecutor () один раз, в начале, и поместить в него длинные методы графического интерфейса пользователя. используя execute (Runnable foo) , как показано ниже (этот код работает в EDT):

private ExecutorService executorService;
::
/*
 * In constructor: create the thread
 */
executorService = Executors.newSingleThreadExecutor();
::
/*
 * When EDT receives a request for a card it calls readCard(),
 * which queues the work out to the *single* thread.
 */
public void readCard() throws Exception {
    executorService.execute(new Runnable() {
        public void run() {
            if (buffer.isEmpty()) {
                /*
                 * fill() takes 1/4 second (simulated by Thread.sleep)
                 * or possibly minutes if we need to have another 
                 * card deck mounted by operator.
                 */
                buffer.fill();
            }
            Card card = buffer.get(); // empties buffer
            /*
             * Send card to CPU
             */
            CPU.sendMessage(card); // <== (A) put card in msg queue
            /* 
             * No race!  Next request will run on same thread, after us.
             */
            buffer.fill(); //         <== (B) pre-fetch next card
            return;
        }
    });
}

Основное различие между этим и SwingWorker заключается в том, что это гарантирует наличие только одного рабочего потока.

7
задан Community 23 May 2017 в 11:55
поделиться