2013-03-14 12 views
7

私はあなたの賢明さに提出したいGUIの問題からのユースケースを持っています。Javaでの巧妙な非同期再描画

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

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

これらのイベントをリッスンする適切なスレッドを作成する方法と、結果の再描画が活発に行われるようにフィルターをかける方法はありますか?理想的には、最初の変更イベントが受信されると直ちに新しい計算を開始するようなものを希望します。

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

私は

を試してみましたが、どのような計算をトリガするためにあまりにも多くのイベントを防止アップデータスレッドのこの低レベルのアプローチを提案している私の友人(A.カルドナ)。私はここ(GPL)のそれを、コピー&ペースト:

彼はスレッドを拡張するクラスでこれを置く:

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

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

各リクエストに対してSwingWorkerを発してください。新しいリクエストが届いてワーカーが終了していない場合は、cancel()と入力して、新しいSwingWorkerを開始してください。他のポスターについては、GUIが処理できるよりも速くイベントを発生させる場合があるので、publish()process()はあなたが探しているものではありません(非常に便利ですが)。それ。

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が既存の操作を取り消すのと同じことを実行していることに注意してください。ただし、新しい要求が発生したときにキャンセルが自動的に行われる点が異なります。

+0

良い解決策。しかし、 'worker1.cancel()'、 'worker1.done()'がまだ実行されていて、 'worker2.done()'の後に実行できる小さなチャンスがあるかどうかは不明です。つまり、古い結果が上書きされます新しい結果。ここでもう少し調査が必要です。 – ZhongYu

+0

これは、リクエストを一括処理する方法の問題を解決しないため、短期間にスレッドを何度もキャンセルして再起動する状況を回避します。 –

+0

リクエストをキャンセルすることは避けられないと思います。リクエストが最後かどうかわからないからです。確かに、より多くのリクエストが来ているかどうかを確認するために、労働者を解雇する前にちょっと待つことができますが、それは単に更新を遅くするだけです。 'worker2.doInBackground()'は 'worker1.cancel()'よりもはるかに時間がかかるので、可能であれば、 –

1

私は、キューを使用して、GUIとコントロールの間にさらに切り離されています。

2つのプロセスの間にBlockingQueueを使用する場合。コントロールが変わるたびに、新しい設定をキューに送信できます。

グラフィックスコンポーネントは、到着するイベントを気に入ったり、必要に応じてそれらを破棄したりすると、キューを読み取ることができます。

1

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

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

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

+0

感謝を行います!それはGUI以外のものにも適用可能でしょうか? –

+1

私は 'process'と' publish'はOPが探しているものだとは思いません。新しいイベントで 'SwingWorker'を更新することはできません。この場合、問題は異なります。処理はUIによってトリガーされるアクションよりも遅くなります。 –

+0

@AndrewMao良い点 - パブリッシュ/プロセスは処理中のUIを更新しています。この問題には、UIからの処理の更新が含まれます。もちろん、UIで生成されたイベントは既にキューに入れられています。たぶん、プログラムはスライダーイベントをキューに入れなくなるまで受信し、以前のものがもはや関係していないので、最新の値で処理を開始するかもしれません。だから私のアイデアはUIの更新に関係していると思いますが、別々に検討するUIから処理の問題があることは間違いありません。 – arcy

1

はjavax.swing.SwingWorker(JavaのJDKソースコード)、2つの方法の間のハンドシェイクを中心とした の実装を見てみましょう:プロセスを公開します。

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

最後の作業要求のみが必要なため、状況に応じてキューを用意する必要はありません。最後の作業要求のみを保存します。いくつかの小さな期間(1秒)に渡って「最後の要求」をサンプリングし、1秒ごとに何度も何度も停止/再起動することを避け、変更された場合は作業をやめて再起動します。


あなたはそのままである/公開プロセスを使用したくない理由はプロセスが常にSwingのイベントディスパッチスレッド上で動作するということである - すべてではない、長時間実行される計算に適しています。

+0

これは彼の使い方によって異なります。すべてのプロセスがUIを更新すれば、それはまさに彼が望むものです。彼はそこで彼の長い計算をする必要はありません(そして、そうすべきではありません)、私は同意します。しかし、それは彼の問題の一部にとってまさに正しいことです。 – arcy

0

ここで重要な点は、進行中の計算を取り消したいということです。計算は、条件を頻繁にチェックして中止する必要があるかどうかを確認する必要があります。

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; 

と計算スレッドスレッドを計算するイベントスレッドからのハンドオーバのparamに

volatile Param newParam; 

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

    return result 
} 

public void run() 
    while(true) 
     Param param = take(); 
     Result result = compute(param); 
     if(result!=null) 
      paint result in event thread 
+0

私は「頻繁に条件をチェックする」、別名ポーリングについて知らない。これは割り込みのためのものです。 –

+0

どのようにビジーループを中断しますか:) – ZhongYu

関連する問題