2016-08-30 133 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

你可能会发布一个完整的代码块,否则很难理解发生了什么感谢 –