我是多线程的新手。我有一个带有标签和进度条的winform。
我想显示处理结果。首先,我使用Application.DoEvents()
方法。但是,我发现表格很冷。
然后我在MSDN上阅读了一些关于多线程的文章。
其次,我使用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()中使用委托并调用它并不会抛出异常。但形式再次冻结。怎么做才能使表格在处理时可以放置/悬垂?
答案 0 :(得分:9)
调用UI线程。例如:
void SetControlText(Control control, string text)
{
if (control.InvokeRequired)
control.Invoke(SetControlText(control, text));
else
control.Text = text;
}
答案 1 :(得分:9)
您只能从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
}
}
答案 2 :(得分:6)
避免致电Application.DoEvents
。通常情况下,这会导致比解决的问题更多的问题。它通常被认为是不好的做法,因为存在更好的替代方案,可以保持用户界面的消息。
避免更改设置CheckForIllegalCrossThreadCalls = false
。这不能解决任何问题。它只掩盖了问题。问题是您正在尝试从主UI线程以外的线程访问UI元素。如果您禁用CheckForIllegalCrossThreadCalls
,那么您将不再获得异常,而是您的应用程序将以不可预测和惊人的方式失败。
在DoWork
事件处理程序中,您需要定期调用ReportProgress
。从您的Form
开始,您需要订阅ProgressChanged
个活动。从ProgressChanged
事件处理程序中访问UI元素是安全的,因为它会自动封送到UI线程中。
答案 3 :(得分:5)
您应该使用Control.Invoke()。有关详细信息,另请参阅this question。
答案 4 :(得分:5)
最干净的方法就是使用BackGroundWorker
。
你错过了一些观点:
DoWork
事件处理程序中的表单元素,因为这会导致跨线程方法调用,这应该在ProgressChanged
事件处理程序中完成。BackGroundWorker
不允许报告进度,也不允许取消操作。当您在代码中添加BackGroundWorker
时,如果要调用该代码,则必须将WorkerReportsProgress
的{{1}}属性设置为BackGroundWorker
true
。{/ li>的ReportProgress
方法
BackGroundWorker
设置为true,并在WorkerSupportsCancellation
事件处理程序的循环中检查您的DoWork
中的属性CancellationPending
我希望我帮助
答案 5 :(得分:4)
您所显示的代码存在一些基本问题。正如其他人所提到的,Application.DoEvents()
将无法从后台线程中执行您想要的操作。将慢速后台处理分成BackgroundWorker
并更新UI线程中的进度条。您正在从后台线程调用progressBar1.Value++
,这是错误的。
永远不要拨打Control.CheckForIllegalCrossThreadCalls = false
,这只会隐藏错误。
如果要从后台线程更新进度条,则必须实现ProgressChanged
处理程序;你没有这样做。
如果您需要实施BackgroundWorker
的示例,请参阅代码项目中的文章BackgroundWorker Threads。
答案 6 :(得分:3)
我认为编写线程安全应用程序是一个比较难理解和正确操作的事情。你真的需要阅读它。如果您从一本书中学习了C#,请回过头来看看是否没有关于多线程的章节。我从Andrew Troelsen的书中了解到(最新的是 Pro C#2005和.NET 2.0平台),直到第14章他都没有触及这个主题(所以当时有很多人已经放弃了阅读) 。我来自嵌入式编程背景,其中并发性和原子性也是关注点,所以这不是.NET或Windows特定的问题。
这里的很多帖子都详细介绍了通过.NET提供的工具处理线程的机制。所有宝贵的建议和你必须学习的东西,但如果你先熟悉“理论”,它将会有所帮助。采取跨线程问题。真正发生的是UI线程有一些内置的复杂性,可以检查你是否从另一个线程修改一个控件。 MS的设计人员意识到这是一个容易犯的错误,因此可以对其进行内置保护。为什么危险?这需要您了解原子操作是什么。由于控件的“状态”不能在原子操作中更改,因此一个线程可以开始更改控件,然后另一个线程可以变为活动状态,从而使控件处于部分修改状态。现在,如果你告诉用户界面我没有给出一个废话只是让我的线程修改控件,它可能会工作。但是,当它不起作用时,你将很难找到bug。你将使代码的可维护性降低,跟随你的程序员会诅咒你的名字。
因此UI非常复杂并且会检查您,您正在编写的类和线程不会。您必须了解类和线程中的原子。您可能会在线程中导致“冻结”。最常见的是,应用程序冻结是死锁情况的结果。在这种情况下,“冻结”意味着UI永远不会再次响应。无论如何,这已经太长了,但我认为至少你应该熟悉“锁定”的使用,可能还有Monitor,interlocked,semaphore和mutex。
只是为了给你一个想法:(来自Troelsen的书)
intVal++; //This is not thread safe
int newVal = Interlocked.Increment(ref intVal); //This is thread safe
.NET还提供[同步]属性。这可以使编写线程安全类变得容易,但是你确实为使用它而付出了高昂的代价。好吧,我只是希望能让你了解所涉及的复杂性,并激励你去做进一步的阅读。
答案 7 :(得分:2)
这样做,创建一个新线程
Thread loginThread = new Thread(new ThreadStart(DoWork));
loginThread.Start();
在ThreadStart()内部,传递要执行的方法。如果你想在这个方法中更改一些控件属性,那么创建一个委托并将其指向一个方法,在该方法中你将编写控件更改的属性,
public delegate void DoWorkDelegate(ChangeControlsProperties);
并调用控件属性这样做,声明一个方法并在其中定义控件new porperties
public void UpdateForm()
{
// change controls properties over here
}
然后以这种方式将代表指向该方法
InvokeUIControlDelegate invokeDelegate = new InvokeUIControlDelegate(UpdateForm);
然后当你想在任何地方更改属性时,只需调用它,
this.Invoke(invokeDelegate);
希望这段代码片段对您有所帮助! :)
答案 8 :(得分:1)
要添加到freedompeace的解决方案,他是对的。此解决方案为thread-safe
,这正是您想要的。您只需使用BeginInvoke
代替Invoke
。
void SetControlText(Control control, string text)
{
control.BeginInvoke(
new MethodInvoker(() =>
{
control.Text = text;
})
);
}
以上修复是最简单,最干净的。
希望这会有所帮助。 :)