我认为我明白为了让一个单独的线程在winforms应用程序中对GUI进行更改,需要调用该方法。但是,我已经编写了一种异步填充组合框的方法,它向我展示了故事的更多内容。
以下是相关代码,省略了公司信息:
private List<string> ids = new List<string>();
private BindingSource bindingSource = new BindingSource();
//...
cboIds.DataSource = bindingSource;
private void GetAvailableIds()
{
Task idTask = new Task(
() =>
{
bindingSource.Add("Searching..."); //This always updates the UI
//without invoking
if (cboIds.InvokeRequired)
{
Invoke((MethodInvoker)delegate
{ //This sometimes updates the UI without
cboIds.Enabled = false; //invoking, but sometimes fails, so I
}); //added the check
}
else
cboIds.Enabled = false;
List<string> temp = GetUpcomingIds();
Invoke((MethodInvoker)delegate
{
cboIds.Enabled = true;
bindingSource.Clear();
foreach (string str in temp)
bindingSource.Add(str); //This never works without invoking.
}); //Why, if the same operation above
}); //always works without invoking?
idTask.Start();
}
为什么BindingSource的初始添加不需要调用,将combobox.enabled设置为false有时需要调用,并且总是需要调用BindingSource的最终添加?如果它们都在同一个线程上,那么它们的行为是否应该相同?我错误地假设他们都在同一个线程上吗?
答案 0 :(得分:0)
当需要Invoke()
时,或者需要异步等效BeginInvoke()
时:当您想要与线程中的任何Control
类对象进行交互时,需要使用它除了拥有该对象的线程。 Control
个对象由创建它们的线程拥有(此所有权归因于托管Control
对象所代表的本机数据结构的底层线程关联)。
如果与提供的代码示例不同,您确实将BindingSource
对象附加到某个控件,那么在执行修改绑定数据的该对象的任何成员时,您需要使用Invoke()
,如果您在一个线程中执行该成员,而该线程不是拥有BindingSource
绑定到的控件的线程。这是因为BindingSource
对象将引发控件处理的事件;该交互必须在拥有该控件的线程中发生,并且您实现该操作的唯一方法是使用BindingSource
将Invoke()
的更新执行移动到拥有线程。方法
如果BindingSource
对象开始没有附加到控件上,然后再附加,则对于对象的任何修改都不需要Invoke()
,但不能在时间点之后它附在一个控件上。如果您的代码示例未完成,并且您实际上已将您创建的bindingSource
实例附加到控件,但在第一次调用Add()
方法后,这可以解释为什么您稍后需要Invoke()
,但不能初次调用Add()
。
现在,考虑到所有这些......
请注意,对于DataSource
绑定,如果您尝试直接更新控件,则不会像修改InvalidOperationException
一样BindingSource
。相反,绑定会延迟控件的更新,直到它能够执行,即在拥有控件的线程上更新BindingSource
。
这意味着在您的代码示例中,当您在调用第一个Add()
方法时,控件未立即更新时,您没有注意到这一点,因为您确实回到了在此之后立即拥有线程,此时可以更新控件。即从技术上讲,你做需要使用Invoke()
进行第一次更新,但这一切都发生得如此之快,以至于你没有注意到“正确的方式”和“正确的方式”之间的区别“错误的方式“。