[英]C# Cross-thread operation not valid in BackgroundWorker
I'm adding items to a listbox when the main form is loaded:加载主窗体时,我将项目添加到列表框:
private void MainForm_Load(object sender, EventArgs e)
{
Dictionary<string, string> item = new Dictionary<string, string>();
item.Add("Test 1", "test1");
item.Add("Test 2", "test 2");
cmbTest.DataSource = new BindingSource(item, null);
cmbTest.DisplayMember = "Key";
cmbTest.ValueMember = "Value";
}
Then I trying to get the selected item value in a BackgroundWorker but it's fails.然后我试图在 BackgroundWorker 中获取选定的项目值,但它失败了。
private void TestWorker_DoWork(object sender, DoWorkEventArgs e)
{
string test = ((KeyValuePair<string, string>)cmbTest.SelectedItem).Value;
MessageBox.Show(test);
}
The backgroundworker should not try to do anything with the UI.后台工作者不应尝试对 UI 做任何事情。 The only thing that the thread can do is notify those who are interested that the background worker calculated something noteworthy.线程唯一可以做的就是通知那些感兴趣的人后台工作人员计算了一些值得注意的事情。
This notification is done using the Event ProgressChanged.此通知是使用 Event ProgressChanged 完成的。
You probably simplified your problem, but the backgroundworker should not read the selected value of the combo box.您可能简化了您的问题,但后台工作人员不应读取组合框的选定值。 If the combobox changes, the Form should start the backgroundworker while passing the value of the selected combobox item.如果 combobox 发生变化,表单应在传递所选 combobox 项的值时启动后台工作程序。
So let's make the problem a bit more interesting: if the user selects an item in comboBox1, the backgroundworker is ordered to calculate something with the selected combobox value.因此,让我们把问题变得更有趣一点:如果用户在 comboBox1 中选择了一个项目,后台工作人员将被命令使用所选的 combobox 值计算一些东西。
During the calculation the BackGroundWorker regularly notifies the form about progress and intermediate calculated values.在计算期间,BackGroundWorker 会定期通知表单有关进度和中间计算值的信息。 When it finishes, the end result is returned.完成后,返回最终结果。
The code will be like this:代码将是这样的:
private void InitializeComponent()
{
this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker();
this.SuspendLayout();
this.backgroundWorker1.DoWork += new DoWorkEventHandler(this.DoBackgroundWork);
this.backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(this.NotifyProgress);
this.backgroundWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(
this.OnBackgounrWorkCompleted);
...
}
When an item is selected comboBox1 the backgroundworker is started using the selected value. When an item is selected comboBox1 the backgroundworker is started using the selected value. While the backgroundworker is started, users can't change combobox1 again, because we can't start the same backgroundworker while it is still busy.当 backgroundworker 启动时,用户无法再次更改 combobox1,因为我们无法在它仍然忙碌时启动同一个 backgroundworker。
Therefore the combobox is disabled, and a progressbar is shown.因此 combobox 被禁用,并显示一个进度条。 During the calculations the progressbar is updated and intermediate results are shown in Label1.在计算过程中,进度条会更新,中间结果显示在 Label1 中。 When the backgroundworker finishes, the progressbar is removed, the final result is displayed in Lable1 and the combobox is enabled again.当 backgroundworker 完成后,进度条被移除,最终结果显示在 Lable1 中并再次启用 combobox。
Note that the rest of the form is still working while the backgroundworker is calculating.请注意,后台工作人员正在计算时,表单的 rest 仍在工作。
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
ComboBox comboBox = (ComboBox)sender;
// disable ComboBox, show ProgressBar:
comboBox.Enabled = false;
this.progressBar1.Minimum = 0;
this.progressBar1.Maximum = 100;
this.progressBar1.Value = 0;
this.progressBar1.Visible = true;
// start the backgroundworker using the selected value:
this.backgroundWorker1.RunWorkerAsync(comboBox.SelectedValue);
}
The Background work:背景工作:
private void DoBackgroundWork(object sender, DoWorkEventArgs e)
{
// e.Argument contains the selected value of the combobox
string test = ((KeyValuePair<string, string>)e.Argument;
// let's do some lengthy processing:
for (int i=0; i<10; ++i)
{
string intermediateText = Calculate(test, i);
// notify about progress: use a percentage and intermediateText
this.backgroundWorker1.ReportProgress(10*i, intermediateText);
}
string finalText = Calculate(test, 10);
// the result of the background work is finalText
e.Result = finalText;
}
Regularly your Form gets notified about the progress: let it update the ProgressBar and show the intermediate text in Label1您的表单会定期收到有关进度的通知:让它更新 ProgressBar 并在 Label1 中显示中间文本
private void NotifyProgress(object sender, ProgressChangedEventArgs e)
{
this.progressBar1.Value = e.ProgressPercentage;
this.label1.Text = e.UserState.ToString();
}
When the BackgroundWorker completes, the final text is displayed in label1, the progressbar disappears and the Combobox is enabled again:当 BackgroundWorker 完成时,最终文本显示在 label1 中,进度条消失,Combobox 再次启用:
private void OnBackgoundWorkCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.label1.Text = e.Result.ToString();
this.progressBar1.Visible = false;
this.comboBox1.Enabled = true;
}
The reason is the background worker runs on separate threads.原因是后台工作程序在单独的线程上运行。 You have to use UI thread to read values from UI thread.in this case, cmbTest is on the UI您必须使用 UI 线程从 UI 线程读取值。在这种情况下,cmbTest 在 UI 上
this.Invoke(new Action(() =>
{
string test = ((KeyValuePair<string, string>)cmbTest.SelectedItem).Value;
}));
If you need values to do async proccess如果您需要值来执行异步处理
private void TestWorker_DoWork(object sender, DoWorkEventArgs e)
{
string test;
this.Invoke(new Action(() =>
{
//Any other things you need from UI thread
test = ((KeyValuePair<string, string>)cmbTest.SelectedItem).Value;
}));
//Here you have access to UI thread values
}
The UI elements can only be accessed by the UI thread. UI 元素只能由 UI 线程访问。
If you are working with WinForms so you can use Control.InvokeRequired
flag and Control.Invoke
metod:如果您使用的是 WinForms,那么您可以使用Control.InvokeRequired
标志和Control.Invoke
方法:
private void TestWorker_DoWork(object sender, DoWorkEventArgs e)
{
if(!cmbTest.InvokeRequired)
{
string test = ((KeyValuePair<string, string>)cmbTest.SelectedItem).Value;
MessageBox.Show(test);
}
else
{
string test;
Invoke(() => test = ((KeyValuePair<string, string>)cmbTest.SelectedItem).Value);
Invoke(() => MessageBox.Show(test));
}
}
Control.InvokeRequired
gets a value indicating whether the caller must call an invoke method when making method calls to the control because the caller is on a different thread than the one the control was created on. Control.InvokeRequired
获取一个值,该值指示调用方在对控件进行方法调用时是否必须调用 invoke 方法,因为调用方所在的线程与创建控件的线程不同。
Control.Invoke
executes the specified delegate on the thread that owns the control's underlying window handle. Control.Invoke
在拥有控件的基础 window 句柄的线程上执行指定的委托。
If you are working with WPF than there is a solution for it too.如果您使用的是 WPF,那么也有解决方案。 Dispatcher.Invoke
executes the specified delegate synchronously on the thread the Dispatcher is associated with: Dispatcher.Invoke
在 Dispatcher 关联的线程上同步执行指定的委托:
private void TestWorker_DoWork(object sender, DoWorkEventArgs e)
{
this.Dispatcher.Invoke(() => {
string test = ((KeyValuePair<string, string>)cmbTest.SelectedItem).Value;
MessageBox.Show(test);
});
}
You can read more about thread-safe calls to windows forms controls of about WPF Threading Model on Microsoft docs.您可以在 Microsoft 文档上阅读更多关于windows forms 控件的线程安全调用WPF Threading Model的信息。
Other solution.其他解决方案。
Declare form field:声明表单域:
private string _value;
Enter the value in this field from the UI control in the place where you run BackgroundWorker
从运行BackgroundWorker
的地方的 UI 控件中在此字段中输入值
private void RunWorkButton_Click(object sender, EventArgs e)
{
_value = ((KeyValuePair<string, string>)cmbTest.SelectedItem).Value;
testWorker.RunWorkerAsync();
}
Next, use this field in the DoWork
method:接下来,在DoWork
方法中使用这个字段:
private void TestWorker_DoWork(object sender, DoWorkEventArgs e)
{
// use _value somehow
string test = _value;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.