2013-08-30 37 views
0

幾天前,我開始在我的WPF應用程序中使用任務來實現並行化。我的應用程序需要每5秒執行一些工作。這項工作必須由4個任務並行完成。除此之外,後臺工作人員需要實施以避免在按任務完成工作時凍結UI。我發現了很多例子來理解任務是如何工作的。但是,我找不到任何簡單的例子來理解任務如何與定時器,後臺工作人員和課程鎖定在一起。我根據自己的理解寫了一個簡單的例子。請給我建議我是否正確地做。這樣,我將更好地理解WPF中的多任務處理。我期待着您的回覆。WPF多任務以及計時器和後臺工作人員

 namespace timer_and_thread 
      { 
       /// <summary> 
       /// Interaction logic for MainWindow.xaml 
       /// </summary> 
       public partial class MainWindow : Window 
       { 
        DispatcherTimer TimerObject; 
        Task[] tasks; 
        readonly object _countLock = new object(); 
        int[] Summ = new int[10]; 
        int Index = 0; 
        int ThreadsCounter = 0; 


        public MainWindow() 
        { 
         InitializeComponent(); 
         TimerObject = new DispatcherTimer(); 
         TimerObject.Tick += new EventHandler(timer_Elapsed); 
         TimerObject.Interval = new TimeSpan(0, 0, 5); 
        } 


        // call the method every 5 seconds 
        private void timer_Elapsed(object sender, EventArgs e) 
        { 
         TimerObject.Stop(); 

         // 4 - is the tasks' count 
         ThreadsCounter = 4; 
         Index = 0; 

         tasks = new Task[4]; 
         tasks[0] = Task.Factory.StartNew(() => DoSomeLongWork()); 
         tasks[1] = Task.Factory.StartNew(() => DoSomeLongWork()); 
         tasks[2] = Task.Factory.StartNew(() => DoSomeLongWork()); 
         tasks[3] = Task.Factory.StartNew(() => DoSomeLongWork()); 

         // wait untill all tasks complete 
         while (ThreadsCounter != 0) ; 


         TimerObject.Start(); 

        } 


        private void DoSomeLongWork() 
        { 
         while (Index < Summ.Length) 
         { 


          // lock the global variable from accessing by multiple threads at a time 
         int localIndex = Interlocked.Increment(ref Index) - 1; 

          //I wrote rundom number generation just a an example of doing some calculation and getting some result. It can also be some long calculation. 
          Random rnd = new Random(); 
          int someResult = rnd.Next(1, 100000); 

          // lock the global variable (Summ) to give it the result of calculation 
          lock (_countLock) 
          { 
           Summ[localIndex] = someResult; 
          }  
         } 


         Interlocked.Decrement(ref ThreadsCounter); 
         return; 

        } 

        // button by which I start the application working 
        private void Start_Button_Click_1(object sender, RoutedEventArgs e) 
        { 
         TimerObject.Start(); 
        } 

       } 
      } 

我有2個額外的問題:

  1. 我可以使用任務,而不是後臺工作的?

  2. 正如我所知,鎖定用於防止線程訪問全局變量。但是,要通過線程訪問UI元素,我應該使用分派器。對?

Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => 
    { 

label1.Content = "Some text"; 
})); 
+0

1)是嘗試Task.Factory.StartNew(()=> StartThreads(NULL,NULL));而不是後臺工作人員自己看2)正確:) – sevdalone

+0

感謝您的回答。代碼如何,是否將任務,計時器和BackgroundWorker一起實現? –

+0

關鍵是,後臺工作者什麼都不做。你不需要它來啓動任務,這些任務在自己的線程中運行,無論如何 –

回答

4

在我看來好像你甚至都不需要BackgroundWorker。目前您有:

private void timer_Elapsed(object sender, EventArgs e) 
{ 
    TimerObject.Stop(); 

    BackgroundWorker backgroundWorkerObject = new BackgroundWorker(); 
    backgroundWorkerObject.DoWork += new DoWorkEventHandler(StartThreads); 
    backgroundWorkerObject.RunWorkerAsync(); 

    TimerObject.Start(); 
} 


private void StartThreads(object sender, DoWorkEventArgs e) 
{ 
    tasks = new Task[4]; 
    tasks[0] = Task.Factory.StartNew(() => DoSomeLongWork()); 
    tasks[1] = Task.Factory.StartNew(() => DoSomeLongWork()); 
    tasks[2] = Task.Factory.StartNew(() => DoSomeLongWork()); 
    tasks[3] = Task.Factory.StartNew(() => DoSomeLongWork()); 

    // Give the tasks a second to start. 
    Thread.Sleep(1000); 
} 

所有BackgroundWorker確實是啓動線程,然後一些未知的原因,然後再繼續等待第二。我看不出有什麼特別的原因讓你等待一秒鐘,然後讓BackgroundWorker退出。

如果刪除不必要的拖延,那麼你可以更改您的代碼:

private void timer_Elapsed(object sender, EventArgs e) 
{ 
    TimerObject.Stop(); 

    tasks = new Task[4]; 
    tasks[0] = Task.Factory.StartNew(() => DoSomeLongWork()); 
    tasks[1] = Task.Factory.StartNew(() => DoSomeLongWork()); 
    tasks[2] = Task.Factory.StartNew(() => DoSomeLongWork()); 
    tasks[3] = Task.Factory.StartNew(() => DoSomeLongWork()); 

    TimerObject.Start(); 
} 

你有另一個問題是,如果這些任務沒有完成再經過計時器事件發生前,你將覆蓋Tasks數組,並且您的計算(特別是Sum數組)將包含來自兩組計算的信息。如果這些線程花費超過五秒鐘的時間完成,那麼在這裏使用全局變量會給您帶來麻煩。

我打算假設你的DoSomeLongWork方法只是一個佔位符,不會批評它。不過,我會提到,爲了增加共享變量,不需要鎖定。而不是:

int localIndex; 
// lock the global variable from accessing by multiple threads at a time 
lock (_countLock) 
{ 
    localIndex = Index; 
    Index++; 
} 

你可以寫:

localIndex = Interlocked.Increment(ref Index, 1) - 1; 

Interlocked.Increment原子遞增值,並返回新值。

+0

謝謝。我做了你推薦我的改變。我也改變了啓動計時器的方式。當所有任務完成其工作時,它只啓動一次。請看看我是否做得正確。現在我有一個while循環 - while(ThreadsCounter!= 0)凍結了我的UI。我應該怎麼做才能防止UI凍結? –

1

要回答8月30日以來的問題,您可以查看ContinueWhenAll函數而不是while循環,並將計時器的開始移動到該位置。它需要一個任務列表,然後當所有任務完成時,創建另一個任務並運行該任務。這樣可以防止UI凍結,但如果它每5秒運行一次,而不是在完成之前的任務後每5秒運行一次,則可能需要查看不同的東西。

http://msdn.microsoft.com/en-us/library/dd321473%28v=vs.110%29.aspx