我正在创建一个带有进度条的Windows窗体程序,该进度条将处理包含许多行的文件。当我执行以下代码时,button2_Click方法中的progressBar1.Maximum调用执行得很好,但PostIncident方法中的那个调用会导致System.InvalidOperationException,其中指出:"跨线程操作无效:控制&#39 ; progressBar1'从其创建的线程以外的线程访问。"
到目前为止,我尝试过一些途径: 1)使PostIncident返回一个bool或magic值,以便我将progressbar1.Maximum调用拉入button2_Click方法。 我的问题是我的线程技能不足以解决从线程中捕获返回值的问题。
2)尝试在进度栏中放置一个锁或信号量。在PostIncident方法中调用最大值,导致同样的错误。
目前,我的解决方案是简单地从项目中删除线程,但我确信有一个优雅的解决方案,我根本没有经验可见。
public partial class ExperianTriggerPoster : Form
{
private readonly OpenFileDialog _ofd = new OpenFileDialog();
public delegate void BarDelegate();
private string _path;
private void button1_Click(object sender, EventArgs e)
{
if (_ofd.ShowDialog() != DialogResult.OK) return;
textBox1.Text = _ofd.SafeFileName;
_path = _ofd.FileName;
}
private void button2_Click(object sender, EventArgs e)
{
string[] sAllLinesFromFile = File.ReadAllLines(_path);
foreach (string line in sAllLinesFromFile)
{
if (!line.StartsWith("N"))
{
progressBar1.Maximum -= 1;
continue;
}
//some logic here...
ThreadPool.QueueUserWorkItem(x => PostIncident(//some parameters here...));
}
}
private void PostIncident(//some parameters here...)
{
//some logic here...
if (customerNo == "not found") // must find a way to make this call thread-safe
{
Log.Information("Could not find customer# for user#: " + userNo);
progressBar1.Maximum -= 1;
}
Invoke(new BarDelegate(UpdateBar));
}
private void UpdateBar()
{
progressBar1.Value++;
if (progressBar1.Value != progressBar1.Maximum) return;
var postingComplete = MessageBox.Show("The posting is complete!", "Experian Trigger Poster", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
if (postingComplete == DialogResult.OK) Environment.Exit(0);
}
}
答案 0 :(得分:2)
查看Safe, Simple Multithreading in Windows Forms或How to: Make Thread-Safe Calls to Windows Forms Controls。
您想在UI线程上调用UI更新。
故障:
Dunno如果这对你来说太先进了,但这确实可以帮助你编写最佳实践。
答案 1 :(得分:1)
由于PostIncident()在一个单独的线程中运行,因此它无权更新表单中的值。这只能从UI线程完成。
有很多方法可以解决这个问题,但标准方法可以在本文中找到:
How to: Make Thread-Safe Calls to Windows Forms ControlŠ
谷歌或搜索StackOverflow for" InvokeRequired"了解更多信息。
答案 2 :(得分:1)
这很简单。
Invoke(new BarDelegate(() => progressBar1.Maximum -= 1));
如你所知
Invoke(new BarDelegate(UpdateBar));
请记住,只有UI线程才能访问UI控件。尝试在非UI线程中分配或读取UI控件的任何属性时,会出现异常。
在您的情况下,ThreadPool.QueueUserWorkItem
在单独的后台主题中运行PostIncident
。为了操作UI控件,只需使用Invoke
包装语句。
Invoke
将语句带入UI线程并执行语句,然后继续返回非UI线程。它充当UI和非UI线程之间的桥梁。
此外,如果您不希望UI线程减慢后台线程,则可以使用BeginInvoke
。在继续非UI线程之前,BeginInvoke
不等待UI线程执行。小心种族问题。