2016-08-30 132 views
2

我有一個蒙特卡洛仿真運行跨多個線程與進度條通知用戶如何去。進度條管理使用Invoke在單獨的線程中完成,但表單未更新。C#,多線程 - 表單不更新

這裏是我的代碼:

Thread reportingThread = new Thread(() => UpdateProgress(iSims, ref myBag)); 
reportingThread.Priority = ThreadPriority.AboveNormal; 
reportingThread.Start();` 

,這裏是被調用的函數:

private void UpdateProgress(int iSims, ref ConcurrentBag<simResult> myBag) 
{ 
    int iCount; 
    string sText; 

    if (myBag == null) 
     iCount = 0; 
    else 
     iCount = myBag.Count; 

    while (iCount < iSims) 
    { 
     if (this.Msg.InvokeRequired) 
     { 
      sText = iCount.ToString() + " simultions of " + iSims + " completed."; 
      this.Msg.BeginInvoke((MethodInvoker) delegate() { this.Msg.Text = sText; this.Refresh(); }); 
     } 
     Thread.Sleep(1000); 
     iCount = myBag.Count; 
    } 
} 

我都用了Application.DoEvents()和this.refresh(),試圖迫使要更新的表單,但沒有任何反應。

更新:這是因爲你可以看到,有一些更新的表格我調用調用之前,他們都做工精細的過程調用上述功能

private void ProcessLeases(Boolean bValuePremium) 
    { 
     int iSims, iNumMonths, iNumYears, iIndex, iNumCores, iSimRef; 
     int iNumSimsPerThread, iThread, iAssets, iPriorityLevel; 
     string sMsg; 
     DateTime dtStart, dtEnd; 
     TimeSpan span; 
     var threads = new List<Thread>(); 
     ConcurrentBag<simResult> myBag = new ConcurrentBag<simResult>(); 
     ConcurrentBag<summaryResult> summBag = new ConcurrentBag<summaryResult>(); 

     this.Msg.Text = "Updating all settings"; 
     Application.DoEvents(); 
     ShowProgressPanel(); 

     iSims = objSettings.getSimulations(); 
     iNumCores = Environment.ProcessorCount; 

     this.Msg.Text = "Initialising model"; 
     Application.DoEvents(); 
     iNumSimsPerThread = Convert.ToInt16(Math.Round(Convert.ToDouble(iSims)/Convert.ToDouble(iNumCores), 0)); 

     this.Msg.Text = "Spawning " + iNumCores.ToString() + " threads"; 

     for (iThread = 0; iThread < iNumCores; iThread++) 
     { 
      int iStart, iEnd; 

      if (iThread == 0) 
      { 
       iStart = (iThread * iNumSimsPerThread) + 1; 
       iEnd = ((iThread + 1) * iNumSimsPerThread); 
      } 
      else 
      { 
       if (iThread < (iNumCores - 1)) 
       { 
        iStart = (iThread * iNumSimsPerThread) + 1; 
        iEnd = ((iThread + 1) * iNumSimsPerThread); 
       } 
       else 
       { 
        iStart = (iThread * iNumSimsPerThread) + 1; 
        iEnd = iSims; 
       } 
      } 
      Thread thread = new Thread(() => ProcessParallelMonteCarloTasks(iStart, iEnd, iNumMonths, iSimRef, iSims, ref objDB, iIndex, ref objSettings, ref myBag, ref summBag)); 
      switch (iPriorityLevel) 
      { 
       case 1: thread.Priority = ThreadPriority.Highest; break; 
       case 2: thread.Priority = ThreadPriority.AboveNormal; break; 
       default: thread.Priority = ThreadPriority.Normal; break; 
      } 
      thread.Start(); 
      threads.Add(thread); 
     } 

     // Now start the thread to aggregate the MC results 
     Thread MCThread = new Thread(() => objPortfolio.MCAggregateThread(ref summBag, (iSims * iAssets), iNumMonths)); 
     MCThread.Priority = ThreadPriority.AboveNormal; 
     MCThread.Start(); 
     threads.Add(MCThread); 

     // Here we review the CollectionBag size to report progress to the user 
     Thread reportingThread = new Thread(() => UpdateProgress(iSims, ref myBag)); 
     reportingThread.Priority = ThreadPriority.AboveNormal; 
     reportingThread.Start(); 

     // Wait for all threads to complete 
     //this.Msg.Text = iNumCores.ToString() + " Threads running."; 
     foreach (var thread in threads) 
      thread.Join(); 

     reportingThread.Abort(); 

     this.Msg.Text = "Aggregating results"; 
     Application.DoEvents(); 

     this.Msg.Text = "Preparing Results"; 
     Application.DoEvents(); 
     ShowResults(); 
     ShowResultsPanel(); 
    } 

- 在每一種情況下,我我正在使用Application.DoEvents()來更新。

myBag是一個ConcurrentBag,每個蒙特卡羅線程都會將結果轉儲到這個ConcurrentBag中。通過使用Count方法,我可以看到有多少模擬完成並更新了用戶。

+1

你會介意發佈一個完整的代碼塊來測試嗎? –

+0

@Leonardo - 上面添加的代碼。 – John

+0

抱歉堅持,但仍然無法測試。什麼是「this.Msg」?它是一個控件嗎? –

回答

3
foreach (var thread in threads) 
    thread.Join(); 

這是你的問題。你在這裏阻塞,所以直到你所有的線程完成之前,UI線程中都不會更新。

這是一個關鍵點 - .DoEvents()自然發生,每當您附加到用戶界面事件處理程序的代碼塊完成執行時,都會自行發生。作爲開發人員,您的主要職責之一是確保在用戶界面事件處理程序中執行的任何代碼及時完成(最多幾百毫秒)。如果你用這種方式編寫你的代碼,那麼永遠都不需要調用DoEvents() ......永遠。

你應該總是這樣寫你的代碼。

除了性能方面的好處之外,使用線程的一個主要優點是它們本質上是異步的 - 爲了利用這一點,您必須相應地編寫代碼。突破程序習慣是困難的。你需要做的是完全忘掉.Join,並在這裏擺脫ProcessLeases方法 - 讓UI再次控制。

您正在處理線程中的更新,因此您只需要完成通知即可讓您在所有線程完成工作時接收新處理程序。你需要保持跟蹤你的線程 - 有他們各自通知上完成(即:調用一些代表回到UI線程,等上),並在任何方法處理它,你會做這樣的事情

if (allThreadsAreFinished) // <-- You'll need to implement something here 
{ 
     reportingThread.Abort(); 
     this.Msg.Text = "Preparing Results";   
     ShowResults(); 
     ShowResultsPanel(); 
} 

或者,你也可以簡單地在後臺線程中調用ProcessLeases(確保正確調用其中的所有調用),然後用.Join阻止該線程並不重要。你也可以廢除所有對.DoEvents()的混亂電話。

此外,你不需要調用this.Refresh();這裏:

this.Msg.BeginInvoke((MethodInvoker) delegate() { 
     this.Msg.Text = sText; 
     this.Refresh(); 
}); 

如果您不是阻塞UI線程的控制將更新沒有它就好了,你會只添加額外的沒有任何工作。如果您的阻塞UI線程,那麼添加.Refresh()調用將無濟於事,因爲UI線程不會自由執行它,而不會執行上一行。這是編程混亂 - 隨機添加代碼,希望它可以工作,而不是檢查和理解它沒有的原因。


第2章:職場類比。

想象一下,UI線程就像是經理。經理可以通過幾種方式委託任務。正如你所做的那樣,使用.Join有點像經理給每個人一份工作要做 - 喬得到一份工作,露西得到另一份工作,比爾獲得第三名,薩拉獲得第四名。這位經理在每個人都完成後都有後續工作,所以他提出了一個儘快完成的計劃。

在給每個人他們的任務之後,經理立即坐到喬的辦公桌前凝視他,什麼都不做,直到喬完成。當喬完成時,他移動到露西的辦公桌,以檢查她是否完成。如果她不在那裏等到露西結束,然後移動到比爾的辦公桌前,凝視他,直到他完成了......然後移動到薩拉的辦公桌上。

顯然這不是生產力。此外,四名團隊成員中的每一個人都向他們的經理髮送了電子郵件狀態更新(Manager.BeginInvoke -> read your email!),但他沒有閱讀任何一封電子郵件,因爲他已將所有時間都花在辦公桌前,盯着他們,等着他們完成他們的任務。無論如何,他也沒有做過任何其他事情。老闆一直在問是怎麼回事,他的手機一直在響,沒有人更新每週的財務數據 - 沒有。經理一直沒有做任何事情,因爲他決定他需要坐在底部,看他的團隊工作,直到他們完成。

經理沒有迴應...如果您等待,經理可能會再次回覆。你想開除經理嗎?

[YES - 開除他][NO - 繼續等待]

更好,人們可能會認爲,如果管理者只需設置每個人關上的東西的工作,然後與做其他上了車的東西。他所關心的只是當他們完成工作時,所需要的只是一條指令讓他們在工作完成時通知他。 UI線程就像你的應用程序的管理器 - 它的時間非常寶貴,你應該儘可能少地使用它。如果你有工作要做,請委託給工作者線程,並且不要讓經理坐下來等待其他人完成工作 - 讓他們在事情準備好時通知他們,讓經理重新開始工作。

+0

有什麼辦法可以在這些線程運行時繼續更新UI? – John

+1

@John是的,你的「報告線程」已經這樣做了。你所要做的就是*走開*。你的'.Join'調用阻塞了UI線程並阻止它執行它的工作。讓您的方法在啓動線程後立即退出,並且所有內容都將很好地取消。 –

+0

@J - 好的非常感謝。我將定義一種方法來衡量線程何時完成,而不是使用Join方法。 – John

0

那麼代碼很偏,而且一目瞭然,如果

this.Msg.InvokeRequired == false 

下面的代碼沒有得到執行。這可能是問題嗎?

+0

我在代碼中添加了一個else子句,但它沒有任何區別。當我在調試器模式下進行測試時,它總是進入If條件,但沒有更新表單。 – John

+0

你可能會發佈一個完整的代碼塊,否則很難理解發生了什麼感謝 –