0

申し訳ありませんが、少し長いですが、少し複雑です...

SwingWorkerは、APIが完全に可能で正常であると明確に述べているように、チャンクがprocess()で合体して到着した場合に、解決に苦労している1つの厄介な問題を除いて、アプリで完全に期待どおりに機能します。

たとえば、「タスクが発生しています。お待ちください」というJDialogがある場合、問題が発生します。そのため、チャンクが公開され、doInBackground()そこに到着しprocess()てJDialogが設定されます。

doInBackgroundの長いタスクが終了したら、さらに2つのコマンドを「公開」します。1つは「JDialogのメッセージを「GUIが更新されるのを待っています」に変更します」、もう1つは「送信する結果をJTableに入力します」と言います。君"。

これについてのポイントは、TableModelのベクトルを置き換えるために大量の新しいデータをJTableに送信する場合、Swingは実際にそれ自体を更新するのに無視できない時間がかかる可能性があるということです...そのため、ユーザーに伝えたいと思います。 「長いタスクは終了しましたが、SwingがGUIを更新するのを待っています」。

奇妙なことに、これら2つの命令が2つの合体したチャンクとして到着した場合、JDialogは部分的にしか更新できないことがわかります。setTitle( "blab")により、JDialogのタイトルが変更されます...ただし、JDialogに対する他のすべての変更JTableのメインGUI更新が完了するまで保留になります。

チャンクの公開の間にdoInBackgroundでわずかな遅延が発生するように設計すれば、JDialogは正常に更新されます。明らかに、合体したチャンクでは、ループを使用して1つずつ通過するので、各ループの最後にタイマーを配置することを考えました。これは効果がありませんでした。

また、JDialogで「validate」と「paint」と「repaint」の無数の順列を試しました。

したがって、問題は、合体したチャンクを処理する反復の間に、process()内でGUIを更新させる方法です。

注意:私は他のことも試しました。チャンクが複数ある場合は、チャンクを再公開します。これに伴う問題は、物事の非同期性を考えると、doInBackgroundに戻ると、必然的に物事が公開され続けるため、チャンクが間違った順序で公開される可能性があることです。さらに、この種のソリューションはエレガントではありません。

後で...要求に応じて、ここにSSCCEがあります。

import javax.swing.*;
import javax.swing.table.*;
import java.awt.*;
import java.util.*;


class Coalescence extends SwingWorker<Object, Object> {
    int DISPLAY_WAIT_FOR_TASK = 0; int DISPLAY_WAIT_FOR_GUI_UPDATE = 1; int UPDATE_TABLE_IN_GUI = 2; int SET_UP_GUI = 3;

    private Object[][] m_dataTable; 
    private JTable m_table;
    private JFrame m_frame;
    private JOptionPane m_pane;
    private JDialog m_jDialog;
    private FontMetrics m_fontMetrics; 
    private Dimension m_intercellSpacing;

    @Override
  protected Object doInBackground() throws Exception {
        publish( SET_UP_GUI );
        publish( DISPLAY_WAIT_FOR_TASK );
        Random rand = new Random();
        String s = "String for display, one two three four five six seven eight";
        m_dataTable = new Object[ 20000 ][]; 
        for( int i = 0; i < 20000; i++ ){
            Object[] row = new Object[ 20 ];
            for( int j = 0; j < 20; j++ ){
                // random length string - so column width computation has something to do...
                int endIndex = rand.nextInt( 40 );
                row[ j ] = s.substring( 0, endIndex);
            }
            m_dataTable[ i ] = row;
            // slow the "lengthy" non-EDT task artificially for sake of SSCCE
            if( i % 10 == 0 )
                Thread.sleep( 1L );
        }

        publish( DISPLAY_WAIT_FOR_GUI_UPDATE );

        // *** LINE TO COMMENT OUT ***
        Thread.sleep( 100L );

        publish( UPDATE_TABLE_IN_GUI );

        return null;
  }



    protected void process( java.util.List<Object> chunks){
        p( "no chunks " + chunks.size() );

        // "CHUNK PROCESSING LOOP"
        for( int i = 0, n_chunks = chunks.size(); i < n_chunks; i++ ){
            int value = (Integer)chunks.get( i );

            p( "processing chunk " + value );

            if( value == SET_UP_GUI ){
                m_frame = new JFrame();
                m_frame.setPreferredSize( new Dimension( 800, 400 ));
                m_frame.setVisible( true );
                JScrollPane jsp = new JScrollPane();
                jsp.setBounds( 10, 10, 600, 300 );
                m_frame.getContentPane().setLayout( null );
                m_frame.getContentPane().add( jsp );
                m_table = new JTable();
                jsp.setViewportView( m_table );
                m_frame.pack();
            m_fontMetrics = m_table.getFontMetrics( m_table.getFont() );
            m_intercellSpacing = m_table.getIntercellSpacing();
            }
            else if( value == DISPLAY_WAIT_FOR_TASK ){
        m_pane = new JOptionPane( "Waiting for results..." );
        Object[] options = { "Cancel" };
        m_pane.setOptions( options );
        // without these 2 sQLCommand, just pressing Return will not cause the "Cancel" button to fire
        m_pane.setInitialValue( "Cancel" );
        m_pane.selectInitialValue();
        m_jDialog = m_pane.createDialog( m_frame, "Processing");
        m_jDialog.setVisible( true );

            }
            else if ( value == DISPLAY_WAIT_FOR_GUI_UPDATE ){
                // this if clause changes the wording of the JDialog/JOptionPane (and gets rid of its "Cancel" option button)
                // because at this point we are waiting for the GUI (Swing) to update the display
        m_pane.setOptions( null );
        m_pane.setMessage( "Populating..." );
        m_jDialog.setTitle( "Table being populated...");
            }
            else if ( value == UPDATE_TABLE_IN_GUI ){
                Object[] headings = { "one", "two", "three", "four", "five", "six", "one", "two", "three", "four", "five", "six",
                        "one", "two", "three", "four", "five", "six", "19", "20" }; 
                m_table.setModel( new javax.swing.table.DefaultTableModel( m_dataTable, headings ));

                // lengthy task which can only be done in the EDT: here, computing the preferred width for columns by examining 
                // the width (using FontMetrics) of each String in each cell...
                for( int colIndex = 0, n_cols = 20; i < n_cols; i++ ){
              int prefWidth = 0;
              javax.swing.table.TableColumn column = m_table.getColumnModel().getColumn( colIndex );
              int modelColIndex = m_table.convertColumnIndexToModel( colIndex );
              for( int rowIndex = 0, n_rows = m_table.getRowCount(); rowIndex < n_rows; rowIndex++ ){
                Object cellObject = m_table.getModel().getValueAt( rowIndex, modelColIndex );
                DefaultTableCellRenderer renderer = (DefaultTableCellRenderer)m_table.getCellRenderer( rowIndex, colIndex );
                int margins = 0;
                if( renderer instanceof Container ){
                  Insets insets = renderer.getInsets();
                  margins = insets.left + insets.right ;
                }
                Component comp = renderer.getTableCellRendererComponent( m_table, cellObject, true, false, rowIndex, colIndex);
                if( comp instanceof JLabel ){
                  String cellString = ((JLabel)comp).getText();
                  int width = SwingUtilities.computeStringWidth(m_fontMetrics, cellString) + margins;
                  // if we have discovered a String which is wider than the previously set widest width String... change prefWidth
                  if( width > prefWidth ){
                    prefWidth = width;
                  }
                }
              }
              prefWidth += m_intercellSpacing.width;
              column.setPreferredWidth(prefWidth);
            // slow things in EDT down a bit (artificially) for the sake of this SSCCE...
            try {
            Thread.sleep( 20L );
          } catch (InterruptedException e) {
            e.printStackTrace();
          }

                }
                m_jDialog.dispose();
            }
        }
    }

    public static void main( String[] a_args ){
        Coalescence c = new Coalescence();
        c.execute();
        try {
        c.get();
    } catch ( Exception e) {
        e.printStackTrace();
    }
    }

    static void p( String s ){
        System.out.println( s );
    }

}

...プログラムは5つの段階で構成されています:1)GUIを設定します2)「タスクが完了するのを待ちます」というメッセージを表示します3)「長い」非EDTタスク4)メッセージを変更して「GUIがテーブルを更新するのを待つ」と表示されます。5)GUIでテーブルを更新します(その後、JDialog / JOptionPaneを破棄します)。

私が理解していないのは、上記のdoInBackgroundのThread.sleep()行をコメントアウトすると、JDialogの動作がおかしくなる理由です。タイトルは更新されますが、JOptionPaneのテキストは変更されず、「キャンセル」 「」ボタンは削除されません。

違いは、Thread.sleep()行がないと、2つのチャンクが合体して到着し、EDTで次々に実行されることです...私は、最後に短いタイマーを実行するなどのことを試しました。 「チャンク処理ループ」、およびThread.yield()の実験...基本的に、GUIにJDialogとそのすべてのコンポーネントを包括的に更新させようとしています...JTableの更新に進む前に...

どんな考えでもありがたいです。

4

3 に答える 3

2

ひびが入った!--paintImmediately()は魔法を実行します:

m_pane.setOptions(null);
m_pane.setMessage("Populating...");
m_jDialog.setTitle("Table being populated...");
Rectangle rect = m_jDialog.getContentPane().getBounds();
((JComponent)m_jDialog.getContentPane()).paintImmediately( rect );

後で

これに遭遇し、以下の一貫性のないコメントを心配している人にとっては、このコメントは安全に無視できると考えるのが妥当だと思います。まず、paintImmediatelyがEDTの外部で実行するように設計されているという証拠はどこにも見当たりません。次に、デッドロックが発生します。並行性の意味は、2つのスレッド間で共有される可変オブジェクトでのみ発生します。したがって、EDTでのこれらのチャンクのループ反復では、これは間違っていると思います。

上記のコードへの別の変更

awt.Dialog.show()のJava API:「ツールキットはこのメソッドを呼び出したイベントポンプがブロックされている間に別のイベントポンプが実行されることを保証するため、イベントディスパッチスレッドからモーダルダイアログを表示することは許可されています」。これが意味するのは、DISPLAY_WAIT_FOR_TASKがprocess()に配信される最後のチャンクである場合、OKです。m_jDialog.setVisible(true)に続いて別のイベントポンプが実行され、この新しいイベントポンプがprocess()への次の呼び出しを処理します。

逆に、チャンクがDISPLAY_WAIT_FOR_TASKと合体する場合(つまり、同じprocess()呼び出しで別のチャンクがそれに続く場合)、コードはsetVisible(true)でブロックされ、ループは次のチャンクを処理するために移動します。 JOptionPaneは、ユーザーによって、またはプログラムによって「破棄」されていました。

これを防ぎ、このsetVisible()コマンドの直後に実行を継続できるようにするには、単一のコマンドm_jDialog.setVisible(true)を独自の(非EDT)スレッドで実行する必要があります(NB JOptionPaneは実行するように設計されています) EDTまたは非EDTのいずれかで)。

明らかに、JOptionPane用のこの特別なスレッドは、その場で作成することも、利用可能なスレッドプールやExecutorServiceなどから参加させることもできます。

于 2011-11-05T07:41:38.237 に答える
2

JDialogで値を設定している場合、Swingは再描画イベントをスケジュールしています。コードがモデルの構築を実行するとき、これらのイベントはEDTスレッドがアイドル状態になるのをまだ待機しています。作業が完了すると、スレッドはアイドル状態になり、遅延イベントが再生されます。

だから、これを試してみてください:

if ( value == UPDATE_TABLE_IN_GUI )ブロック内のコードを直接実行する代わりに、メソッドに配置します。それへの呼び出しをでラップし、実行のためにそれをスケジュールするためにRunnable使用します。SwingUtilities.invokeLater()

これにより、EDTはテーブルを作成する前にキューに入れられたイベントを処理できるようになります

アップデート

EDTには、実行するキューがありますRunnablesRunnables後で実行するためのSwingコンポーネントキューへの変更。これは一般的に良いことです。ラベルのテキスト、前景、および背景を設定する場合、これらのそれぞれの間で再描画を待つ必要はありません。

EDTは、現在のEDTが終了するまで、次のEDTに進みませんRunnable。process()メソッドは、これらのRunnableの1つから呼び出されます。したがって、EDTに他の更新を実行させる唯一の方法は、から戻ることですprocess()。SwingUtilities.invokeLater()は、これを行う最も簡単な方法です。

JDialogのタイトルに関しては、一部のLAFはそれをネイティブウィンドウマネージャー(XまたはMS Windows)に委任します。タイトルがEDTによってペイントされていない可能性があります。

于 2011-11-04T12:44:32.687 に答える
1

あなたが意味するなら、私は一度にのベクトル全体を設定していますTableModel、確かにそうです。

これが問題の核心かもしれません。フライウェイトパターンでレンダラーJTable使用します。更新を表示可能な行に制限することにより、モデルを段階的に更新するコストが最小限に抑えられます。通常は律速段階であり、簡単なでは通常、を使用してレイテンシをシミュレートします。process()publish() sleep()

TableModelから派生したAは便利ですが、内部DefaultTableModelで使用(同期)されます。は、選択したデータ構造の自由度を高めるための代替手段です。java.util.VectorAbstractTableModel

于 2011-11-03T22:28:51.467 に答える