2011-03-21 4 views
0

저는 멀티 스레드 UI를 작성하고 있습니다. 긴 프로세스를 BackgroundWorker 클래스에서 처리하고 UI에 작은 타이머를 사용하여 프로세스의 소요 시간을 추적하고 싶습니다. 그러한 UI를 구축 한 것은 이번이 처음 이었기 때문에 웹상의 관련 자료를 읽었습니다. 내 테스트 코드는 이렇게이다 :WPF 및 멀티 스레딩 질문

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 요소는 다른 스레드에 있으므로 UI ​​요소에 액세스 할 수 없으므로 대리자를 사용하여 텍스트를 가져와 설정해야했습니다.

작동하지만 더 좋은 방법이 있습니까? 코드는 이러한 기본적인 작업을하는 것처럼 보입니다. 하단의 EndInvoke 비트도 완전히 이해하지 못했습니다. 이 스레드에서 코드 스 니펫을 얻었습니다. Should One Always Call EndInvoke a Delegate inside AsyncCallback?

저는 BeginInvoke의 결과를 EndInvoke라는 아이디어로 이해합니다. 그러나이 상황에서 이것을 사용하는 올바른 방법입니까? 리소스 누출에 대해 걱정할 뿐이지 만 디버깅 할 때 타이머가 작동하기 전에 콜백이 실행되는 것처럼 보입니다.

답변

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 : 실행 시간과 진행 상황은 서로 다릅니다. 미리 잡을 행이 몇 개 있는지 알고 있으면 진행 상황을 업데이트 할 수 있습니다 ('SELECT COUNT'); "얼마나 오랫동안 작업이 실행되고 있는지"에 대해서는 실제로 타이머가 필요합니다. 그러나 타이머와 'BackgroundWorker'사이에는 * 커플 링이 없어야합니다. – Jon

+0

흠. 별도의 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(); 
       } 
      } 

     }