7

私はあなたの賢明さに提出したいGUIの問題から来るユースケースを持っています.

使用事例

ユーザーがGUIで設定したいくつかのパラメーターに応じて計算結果を表示するGUIがあります。たとえば、ユーザーがスライダーを動かすと、いくつかのイベントが発生し、すべてが新しい計算をトリガーします。ユーザーがスライダーの値を A から B に調整すると、多数のイベントが発生します。

ただし、計算には数秒かかる場合がありますが、スライダーの調整では数 100 ミリ秒ごとにイベントが発生する可能性があります。

これらのイベントをリッスンし、結果の再描画が活発になるようにそれらをフィルタリングする適切なスレッドを作成する方法は? 理想的には、次のようなものが必要です

  • 最初の変更イベントが受信されるとすぐに新しい計算を開始します。
  • 新しいイベントが受信された場合は最初の計算をキャンセルし、新しいパラメーターで新しいイベントを開始します。
  • ただし、最後に完了した計算は、最後に更新されたパラメーターを持つものである必要があるため、最後のイベントが失われないようにしてください。

私が試したこと

私の友人 (A. Cardona) は、あまりにも多くのイベントが計算をトリガーするのを防ぐアップデータ スレッドのこの低レベルのアプローチを提案しました。ここにコピーして貼り付けます(GPL):

彼はこれを Thread を拡張するクラスに入れます。

public void doUpdate() {
    if (isInterrupted())
        return;
    synchronized (this) {
        request++;
        notify();
    }
}

public void quit() {
    interrupt();
    synchronized (this) {
        notify();
    }
}

 public void run() {
    while (!isInterrupted()) {
        try {
            final long r;
            synchronized (this) {
                r = request;
            }
            // Call refreshable update from this thread
            if (r > 0)
                refresh(); // Will trigger re-computation
            synchronized (this) {
                if (r == request) {
                    request = 0; // reset
                    wait();
                }
                // else loop through to update again
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


public void refresh() {
    // Execute computation and paint it
    ...
}

パラメータが変更されたことを示すイベントが GUI によって送信されるたびに、 を呼び出しますupdater.doUpdate()。これにより、メソッドのrefresh()呼び出し回数が大幅に減少します。しかし、私はこれを制御できません。

別の方法?

それを行う別の方法があるかどうか疑問に思っていました。それは、jaca.concurrent クラスを使用します。しかし、Executors フレームワークで、何から始めるべきかを分類できませんでした。

同様のユースケースの経験がある人はいますか?

ありがとう

4

5 に答える 5

4

を使用している場合はSwingSwingWorkerこの機能を提供し、スレッドプールを自分で処理する必要はありません。

リクエストSwingWorkerごとにを起動します。新しいリクエストが届き、ワーカーが完了していない場合は、cancel()それを実行して、新しいリクエストを開始するだけSwingWorkerです。他のポスターが言ったことに関して、私はあなたが探しているものではないと思いますpublish()process()それらは非常に便利ですが)。なぜなら、それらはGUIが処理できるよりも速くイベントを発生させる可能性がある場合のためのものだからです。

ThingyWorker worker;

public void actionPerformed(ActionEvent e) {
    if( worker != null ) worker.cancel();
    worker = new ThingyWorker();
    worker.execute();
}

class ThingyWorker extends SwingWorker<YOURCLASS, Object> {
    @Override protected YOURCLASS doInBackground() throws Exception {
        return doSomeComputation(); // Should be interruptible
    }   
    @Override protected void done() {
        worker = null; // Reset the reference to worker

        YOURCLASS data;

        try {
            data = get();
        } catch (Exception e) { 
            // May be InterruptedException or ExecutionException                
            e.printStackTrace();
            return;
        }           

        // Do something with data
    }       
}

アクションとdone()メソッドの両方が同じスレッドで実行されるため、既存のワーカーが存在するかどうかへの参照を効果的にチェックできます。

事実上、これはGUIが既存の操作をキャンセルできるようにするのと同じことを行うことに注意してください。ただし、キャンセルは新しい要求が発生したときに自動的に実行されます。

于 2013-03-14T15:39:36.030 に答える
1

キューを使用して、GUIとコントロールの間の切断をさらに強化します。

BlockingQueue2つのプロセスの間に使用する場合。コントロールが変更されるたびに、新しい設定をキューに投稿できます。

グラフィックコンポーネントは、いつでもキューを読み取り、到着したイベントに対応したり、必要に応じてそれらを破棄したりできます。

于 2013-03-14T15:16:53.060 に答える
1

SwingWorker.publish() ( http://docs.oracle.com/javase/6/docs/api/javax/swing/SwingWorker.html )を調べます

Publish を使用すると、SwingWorker オブジェクトのバックグラウンド スレッドが process() メソッドを呼び出すことができますが、すべての publish() 呼び出しが process() 呼び出しになるわけではありません。process() が返され、再度呼び出すことができるようになる前に複数のプロセス呼び出しが行われた場合、SwingWorker は、複数のパブリッシュ呼び出しに使用されるパラメーターを連結して、1 つのプロセス呼び出しにします。

処理中のファイルを表示する進行状況ダイアログがありました。ファイルは、UI が追いつくよりも速く処理されました。ファイル名を表示するために処理を遅くしたくありませんでした。私はこれを使用し、プロセスにプロセスに送信された最終的なファイル名のみを表示させました。この場合、私が望んでいたのは、現在の処理がどこにあるかをユーザーに示すことだけでした。ユーザーはとにかくすべてのファイル名を読み取るつもりはありませんでした。私のUIはこれで非常にスムーズに機能しました。

于 2013-03-14T15:28:52.290 に答える
1

javax.swing.SwingWorker (Java JDK のソース コード) の実装を見てみましょう。2 つのメソッド ( publishprocess ) 間のハンドシェイクに焦点を当てています。

これらはそのまま問題に直接適用することはできませんが、更新をワーカー スレッドにキューイング (公開) し、ワーカー スレッド (プロセス) でそれらを処理する方法を示しています。

最後の作業要求のみが必要なため、この状況ではキューさえ必要ありません。最後の作業要求のみを保持します。その「最後のリクエスト」を短い期間 (1 秒) にわたってサンプリングし、1 秒ごとに何度も停止/再起動することを避けます。変更された場合は、作業を停止して再起動します。


パブリッシュ/プロセスをそのまま使用したくない理由は、プロセスが常に Swing イベント ディスパッチ スレッドで実行されるためです。長時間実行される計算にはまったく適していません。

于 2013-03-14T15:33:38.500 に答える
0

ここで重要なのは、進行中の計算をキャンセルできるようにすることです。計算は、中断する必要があるかどうかを確認するために、条件を頻繁にチェックする必要があります。

volatile Param newParam;

Result compute(Param param)
{
    loop
        compute a small sub problem
        if(newParam!=null) // abort
            return null;  

    return result
}

イベントスレッドから計算スレッドにパラメータを渡す

synchronized void put(Param param)  // invoked by event thread
    newParam = param;
    notify();

synchronized Param take()
    while(newParam==null)
        wait();
    Param param = newParam;
    newParam=null;
    return param;

そして計算スレッドは

public void run()
    while(true)
        Param param = take();
        Result result = compute(param);
        if(result!=null)
            paint result in event thread
于 2013-03-14T16:23:20.000 に答える