如何在Winform中使用multithreading?

我是multithreading的新手。 我有一个带有标签和进度条的winform。

我想显示处理结果。 首先,我使用Application.DoEvents()方法。 但是,我发现表格很冷。

然后我在MSDN上阅读了一些关于multithreading的文章。

其次,我使用BackgroundWorker来做到这一点。

 this.bwForm.DoWork += (o, arg) => { DoConvert(); }; this.bwForm.RunWorkerAsync(); 

表格不会冻结,我可以在处理时拖动/ drog。 不幸的是,它会抛出InvalidOperationException。 所以我必须使用它。 Control.CheckForIllegalCrossThreadCalls = false; 我确保这不是最终的解决方案。

专家,您有什么建议吗?

编辑:当我调用listview时抛出InvalidOperationException。 此代码位于DoWork()中。

  foreach (ListViewItem item in this.listView1.Items) { //........some operation lbFilesCount.Text = string.Format("{0} files", listView1.Items.Count); progressBar1.Value++; } 

Edit2:我在DoWork()中使用委托和调用,它不会抛出exception。 但forms再次冻结。 怎么做才能使表格在处理时可以放置/悬垂?

您只能从UI线程(WinForm one)设置进度条用户控件属性。 使用BackgroundWorker最简单的方法是使用ProgressChanged事件:

 private BackgroundWorker bwForm; private ProgressBar progressBar; 

在WinForm构造函数中:

 this.progressBar = new ProgressBar(); this.progressBar.Maximum = 100; this.bwForm = new BackgroundWorker(); this.bwForm.DoWork += new DoWorkEventHandler(this.BwForm_DoWork); this.bwForm.ProgressChanged += new ProgressChangedEventHandler(this.BwForm_ProgressChanged); this.bwForm.RunWorkerAsync(); 

 void BwForm_DoWork(object sender, DoWorkEventArgs e) { BackgroundWorker bgw = sender as BackgroundWorker; // Your DoConvert code here // ... int percent = 0; bgw.ReportProgress(percent); // ... } void BwForm_ProgressChanged(object sender, ProgressChangedEventArgs e) { this.progressBar.Value = e.ProgressPercentage; } 

请参阅此处: http : //msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx

编辑

所以我不明白你的问题,我认为这是关于在工作期间显示进度条的进展。 如果是关于工作的结果,请在BwForm_DoWork事件中使用e.Result。 为已完成的事件添加新的事件处理程序并管理结果:

 this.bwForm.RunWorkerCompleted += new RunWorkerCompletedEventHandler(this.BwForm_RunWorkerCompleted); 

 private void BwForm_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { YourResultStruct result = e.Result as YourResultStruct; if (e.Error != null && result != null) { // Handle result here } } 

调用UI线程。 例如:

 void SetControlText(Control control, string text) { if (control.InvokeRequired) control.Invoke(SetControlText(control, text)); else control.Text = text; } 

避免调用Application.DoEvents 。 通常情况下,这会导致比解决的问题更多的问题。 它通常被认为是不好的做法,因为存在更好的替代方案,可以保持UI的消息。

避免更改设置CheckForIllegalCrossThreadCalls = false 。 这不能解决任何问题。 它只掩盖了问题。 问题是您正在尝试从主UI线程以外的线程访问UI元素。 如果您禁用CheckForIllegalCrossThreadCalls那么您将不再获得exception,而是您的应用程序将以不可预测和惊人的方式失败。

DoWork事件处理程序中,您需要定期调用ReportProgress 。 从您的Form您将要订阅ProgressChanged事件。 从ProgressChanged事件处理程序中访问UI元素是安全的,因为它会自动封送到UI线程中。

您应该使用Control.Invoke() 。 有关详细信息,请参阅此问题 。

最干净的方法是像你一样使用BackGroundWorker

你错过了一些观点:

  1. 您无法在DoWork事件处理程序中访问您的表单元素,因为这会使跨线程方法调用,这应该在ProgressChanged事件处理程序中完成。
  2. 默认情况下, BackGroundWorker不允许报告进度,也不允许取消操作。 向代码中添加BackGroundWorker ,如果要调用BackGroundWorkerReportProgress方法,则必须将BackGroundWorker WorkerReportsProgress属性设置为true
  3. 如果您还需要允许用户取消操作,请将WorkerSupportsCancellation设置为true,并在DoWork事件处理程序的循环中检查BackGroundWorker名为CancellationPending的属性

我希望我帮忙

您显示的代码存在一些基本问题。 正如其他人所提到的, Application.DoEvents()将无法从后台线程执行您想要的操作。 将慢速后台处理分离为BackgroundWorker并更新UI线程中的进度条。 您正在从后台线程调用progressBar1.Value++ ,这是错误的。

永远不要调用Control.CheckForIllegalCrossThreadCalls = false ,这只会隐藏错误。

如果要从后台线程更新进度条,则必须实现ProgressChanged处理程序; 你不这样做。

如果您需要实现BackgroundWorker的示例,请参阅Code Project中的文章BackgroundWorker Threads 。

我会说编写线程安全的应用程序是一个比较难理解和正确的事情。 你真的需要阅读它。 如果您从一本书中学习了C#,请回过头来看看是否没有关于multithreading的章节。 我从Andrew Troelsen的书中得知(最新的是Pro C#2005和.NET 2.0平台 ),直到第14章他都没有触及这个主题(所以当时很多人已经放弃了阅读)。 我来自嵌入式编程背景,其中并发性和primefaces性也是关注点,因此这不是.NET或Windows特定的问题。

这里的很多post都详细介绍了通过.NET提供的工具处理线程的机制。 所有有价值的建议和你必须学习的东西,但如果你先熟悉“理论”,它将会有所帮助。 采取跨线程问题。 真正发生的是UI线程有一些内置的复杂性,可以检查你是否从另一个线程修改一个控件。 MS的设计人员意识到这是一个容易犯的错误,因此可以对其进行内置保护。 为什么危险? 这需要您了解primefaces操作是什么。 由于控件的“状态”不能在primefaces操作中更改,因此一个线程可以开始更改控件,然后另一个线程可以变为活动状态,从而使控件处于部分修改状态。 现在,如果你告诉用户界面我没有给出一个废话只是让我的线程修改控件,它可能会工作。 但是,当它不起作用时,你将很难找到bug。 你将使代码的可维护性降低,跟随你的程序员会诅咒你的名字。

因此UI很复杂并且会检查您,您正在编写的类和线程不会。 您必须了解类和线程中的primefaces。 您可能会在线程中导致“冻结”。 最常见的应用程序冻结是死锁情况的结果。 在这种情况下,“冻结”意味着UI永远不会再次响应。 无论如何,这已经太长了,但我认为至少你应该熟悉“锁定”的使用,可能还有监视器,互锁,信号量和互斥量。

只是为了给你一个想法:(来自Troelsen的书)

 intVal++; //This is not thread safe int newVal = Interlocked.Increment(ref intVal); //This is thread safe 

.NET还提供[同步]属性。 这可以使编写线程安全类变得容易,但是您确实为使用它而付出了高昂的代价。 好吧,我只是希望能让你对所涉及的复杂性有所了解,并激励你去做进一步的阅读。

这样做,创建一个新的线程

  Thread loginThread = new Thread(new ThreadStart(DoWork)); loginThread.Start(); 

在ThreadStart()内部,传递要执行的方法。 如果你想在这个方法中更改一些控件属性,那么创建一个委托并将其指向一个方法,在该方法中你将编写控件更改的属性,

  public delegate void DoWorkDelegate(ChangeControlsProperties); 

并调用控件属性这样做,声明一个方法,并在其中定义控件的新属性

  public void UpdateForm() { // change controls properties over here } 

然后以这种方式将代表指向该方法

  InvokeUIControlDelegate invokeDelegate = new InvokeUIControlDelegate(UpdateForm); 

那么当你想在任何地方更改属性时,只需调用它,

  this.Invoke(invokeDelegate); 

希望这段代码片段对您有所帮助! 🙂

要添加到freedompeace的解决方案,他是对的。 这个解决方案是thread-safe ,这正是你想要的。 您只需要使用BeginInvoke而不是Invoke

 void SetControlText(Control control, string text) { control.BeginInvoke( new MethodInvoker(() => { control.Text = text; }) ); } 

以上修复是最简单,最干净的。

希望这可以帮助。 🙂