如何在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
。
你错过了一些观点:
- 您无法在
DoWork
事件处理程序中访问您的表单元素,因为这会使跨线程方法调用,这应该在ProgressChanged
事件处理程序中完成。 - 默认情况下,
BackGroundWorker
不允许报告进度,也不允许取消操作。 向代码中添加BackGroundWorker
,如果要调用BackGroundWorker
的ReportProgress
方法,则必须将BackGroundWorker
的WorkerReportsProgress
属性设置为true
。 - 如果您还需要允许用户取消操作,请将
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; }) ); }
以上修复是最简单,最干净的。
希望这可以帮助。 🙂