2011-03-21 9 views
0

私はマルチスレッドUIの構築に取り組んでいます。長いプロセスをBackgroundWorkerクラスで処理し、UIの小さなタイマーでプロセスの経過時間を把握したいと思います。このようなUIを構築するのは初めてのことなので、ウェブ上の関連リソースを読んでいます。私のテストコードは、このようです:WPF&Multi-threading questions

private BackgroundWorker worker; 
    private Stopwatch swatch = new Stopwatch(); 
    private delegate void simpleDelegate(); 
    System.Timers.Timer timer = new System.Timers.Timer(1000); 
    string lblHelpPrevText = ""; 

    private void btnStart_Click(object sender, RoutedEventArgs e) 
    { 
     try 
     { 
      worker = new BackgroundWorker(); //Create new background worker thread 
      worker.DoWork += new DoWorkEventHandler(BG_test1); 
      worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BG_test1end); 
      worker.RunWorkerAsync(); 

      simpleDelegate del = new simpleDelegate(clockTicker); 
      AsyncCallback callBack = new AsyncCallback(clockEnd); 
      IAsyncResult ar = del.BeginInvoke(callBack, null);          

      lblHelpText.Text = "Processing..."; 
      } 
      finally 
      { 
      worker.Dispose(); //clear resources 
      } 
    } 

    private void clockTicker() 
      {  
       //Grab Text 
       simpleDelegate delLblHelpText = delegate() 
       { lblHelpPrevText = this.lblHelpText.Text; }; 
       this.Dispatcher.BeginInvoke(DispatcherPriority.Send, delLblHelpText); 

       //Start clock 
       timer.Elapsed += new ElapsedEventHandler(clockTick); 
       timer.Enabled = true; 
       swatch.Start(); 
      } 

      private void clockTick(object sender, ElapsedEventArgs e) 
      { 
       simpleDelegate delUpdateHelpTxt = delegate() 
       { this.lblHelpText.Text = String.Format("({0:00}:{1:00}) {2}", swatch.Elapsed.Minutes, swatch.Elapsed.Seconds, lblHelpPrevText); }; 
       this.Dispatcher.BeginInvoke(DispatcherPriority.Send, delUpdateHelpTxt); 
      } 

    private void BG_test1(object sender, DoWorkEventArgs e) 
      { 
       //this.lblHelpText.Text = "Processing for 10 seconds..."; 
       Thread.Sleep(15000); 
      } 

      private void BG_test1end(object sender, RunWorkerCompletedEventArgs e) 
      { 
       this.lblHelpText.Text = "Process done."; 
       this.timer.Enabled = false; 
       this.swatch.Stop(); 
       this.swatch.Reset();    
      } 

static void clockEnd(IAsyncResult ar) 
     { 
      simpleDelegate X = (simpleDelegate)((AsyncResult)ar).AsyncDelegate; 
      X.EndInvoke(ar); 
     } 

アイデアは、ボタンがクリックされたとき、私たちはその後、毎秒その上に時間を追加(「...処理」など)ラベルからステータステキストを取るです。別のスレッド上にあるので、TimerクラスのUI要素にアクセスできませんでした。そのため、代理人を使用してテキストを取得して設定する必要がありました。

これは機能しますが、これを処理するにはより良い方法がありますか?コードは、このような基本的な操作のために非常によく見えます。私はまた、下部のEndInvokeビットを完全に理解していません。このスレッドのコードの一部を入手しましたShould One Always Call EndInvoke a Delegate inside AsyncCallback?

私はBeginInvokeの結果を受け取ることを理解しています。しかし、これをこの状況で使う正しい方法はありますか?リソースのリークが心配ですが、デバッグ時にタイマーが動作する前にコールバックが実行されているように見えます。

答えて

0

タイマーを使用してUIを更新できます。それは通常の習慣です。 System.Timer.Timerの代わりに、System.Windows.Threading.DispatcherTimerを使用することをお勧めします。 DispatcherTimerはDispatcherと同じスレッドで実行されます。また、BackgroundWorkerの代わりにThreadPoolを使用することもできます。ここ は私のサンプルです。このため

object syncObj = new object(); 
Stopwatch swatch = new Stopwatch(); 
DispatcherTimer updateTimer; // Assume timer was initialized in constructor. 

void btnStart_Click(object sender, RoutedEventArgs e) { 

    lock (syncObj) { 
    ThreadPool.QueueUserWorkItem(MyAsyncRoutine); 
    swatch.Start(); 
    updateTimer.Start(); 
    } 
} 

void updateTimer_Tick(object sender, EventArgs e) { 

    // We can access UI elements from this place. 
    lblHelpText.Text = String.Format("({0:00}:{1:00}) Processing...", swatch.Elapsed.Minutes, swatch.Elapsed.Seconds); 
} 

void MyAsyncRoutine(object state) { 

    Thread.Sleep(5000); 

    lock (syncObj) 
    Dispatcher.Invoke(new Action(() => { 
     swatch.Stop(); 
     updateTimer.Stop(); 
     lblHelpText.Text = "Process done."; 
    }), null); 
} 
2

BackgroundWorkerの進捗状況を読み取り、UIを更新するために別のタイマーを使用しないでください。代わりに、BackgroundWorker自身が直接的または間接的にその進捗状況をUIに「公開」するようにしてください。

これは、とにかくやりたいことですが、この場合は組み込みプロビジョニングがあります:BackgroundWorker.ProgressChangedイベントです。

private void BG_test1(object sender, DoWorkEventArgs e) 
{ 
    for(var i = 0; i < 15; ++i) { 
     Thread.Sleep(1000); 
     // you will need to get a ref to `worker` 
     // simplest would be to make it a field in your class 
     worker.ReportProgress(100/15 * (i + 1)); 
    } 

} 

あなたは、単にProgressChangedに独自のハンドラをアタッチし、そこからBeginInvokeを使用してUIを更新することができますこの方法。タイマーとそれに関連するすべてが行くことができます。

+0

おかげで、私は手動での進捗状況を更新する必要はないでしょうか?この場合、操作の実行時間を決定することはできません(SQLデータベースから数千行のデータを取得する予定です)ので、操作の実行時間を記録するタイマーを設定するだけでした。 – MHTri

+0

@MHTri:実行時間と進歩は2つの異なるものです。事前に取得する行数( 'SELECT COUNT')を知っている場合は、進捗状況を更新できます。 「どのくらいの時間操作が実行されているか」については、本当にタイマーが必要です。しかし、*タイマーと 'BackgroundWorker'の間には*カップリングは全くありません。 – Jon

+0

Hmm。私は別のSQLユーティリティクラスの別のメソッドでSQLDataAdapter.Fillを使用しているので、進捗状況をFillブロックとして見積もりしません(つまり、DataTable.Rows.Countプロパティにアクセスできないまた、完了するまで、RowChangedEventの火災は変わらないでしょうか? – MHTri

0
private void button1_Click(object sender, EventArgs e) 
     { 

      string strFullFilePath = @"D:\Print.pdf"; 


      ProcessStartInfo ps = new ProcessStartInfo(); 
      ps.UseShellExecute = true; 
      ps.Verb = "print"; 
      ps.CreateNoWindow = true; 
      ps.WindowStyle = ProcessWindowStyle.Hidden; 
      ps.FileName = strFullFilePath; 
      Process.Start(ps); 
      Process proc = Process.Start(ps); 
      KillthisProcess("AcroRd32"); 


     } 
     public void KillthisProcess(string name) 
     { 

      foreach (Process prntProcess in Process.GetProcesses()) 
      { 

       if (prntProcess.ProcessName.StartsWith(name)) 
       { 
        prntProcess.WaitForExit(10000); 
        prntProcess.Kill(); 
       } 
      } 

     }