跨线程操作无效,即使使用InvokeRequired。

7
我是一名有用的助手,可以将文本翻译成中文。
我有一个带有自定义控件的表单。
我的表单中有一个方法:
private void SetEnabledOnControls(bool val)
{
  if (InvokeRequired)
  {
      Invoke((Action<bool>)SetEnabledOnControls, val);
  }
  else
  {
       //do the work - iterate over child controls, 
       //and they iterate over their children, etc...
  }
}

else分支中的方法内,我得到了上述异常:
Cross-thread operation not valid: Control 'txtNumber' accessed from a thread other than the thread it was created on. 我的情境实际上有点复杂——我只是以此作为例子。实际上正在发生的是,我正在使用WorkflowFoundation - 我有一个在WorkflowApplication(它在自己的线程中运行)中运行的StateMachineActivity(CTP1),我订阅了它的事件,并从那里调用SetEnabledOnControls。此外,我使用书签来恢复我的工作流程(还有MEF在旁边,不涉及情境)。
所有这些都与我对InvokeRequired的明显误解无关——如果InvokeRequired为false,我怎么可能会出现跨线程异常?我没有“手动”创建任何控件——它们都在由设计师放置的Initialize()中。
有人能给我一些启示吗?
谢谢!

编辑 使用GWLlosa的建议,我使用System.Threading.Thread.CurrentThread.ManagedThreadId跟踪了线程ID。现在来到了奇怪的部分... Initialize()中的线程ID是10。在通过前两个状态后,它以ID 13进入 - InvokeRequired为true,并且正确调用了。但是,在第二个状态之后,当它进入SetEnabledOnControls时,它再次变成了13,但这次InvokeRequired为false!怎么回事?当然,后来它无法更改子控件(不足为奇)。难道窗体在某种情况下改变了它所在的线程吗?

编辑2 现在我正在调用:

 if (IsHandleCreated)
 {
     Invoke((Action<bool>)SetEnabledOnControls, val);
 }

它将IsHandleCreated设置为true,但仍然在devSpeed 指向的问题上失败。

编辑3 FACEPALM :) 其中一个按钮最初是Form的CancelButton。当它从属性中被移除后,代码仍然具有DialogResult=Cancel - 因此我的窗体确实关闭了,当然它缺少句柄,所以InvokeRequired没有返回正确的信息,因此出现了错误。

谢谢大家!我今天学到了新东西 :)

2个回答

2

在创建控件时(在Initialize()函数中),记录线程ID,然后再在尝试访问控件之前记录线程ID,这有助于更轻松地进行调试。通常情况下,当您在不同于预期的线程上创建控件时,就会发生此问题。


非常有帮助!请查看我的更新以获取结果... - veljkoz

0
我最近遇到了一个类似的问题,我的函数正在拆除,然后在FlowLayoutPanel中动态创建控件:
        public static void RenderEditorInstance(DataContext dataContext, object selectedItem, Form targetForm, Control targetControl, List<DynamicUserInterface.EditorControl> editorControls, EventHandler ComboBox_SelectedIndexChanged, EventHandler TextBoxControl_TextChanged, EventHandler CheckBox_CheckChanged, EventHandler NumericUpDown_ValueChanged, CheckedListControl.ItemChecked OnItemChecked, EventHandler dateTimePicker_ValueChanged, DynamicUserInterface.DuplicationValidationFailed liveLookupValidationFailed, DynamicUserInterface.PopulateComboBoxCallback populateComboBoxCallback)
        {           if (targetForm.InvokeRequired)
            {
                InstanceRenderer renderer = new InstanceRenderer(RenderEditorInstance);
                targetForm.Invoke(renderer, dataContext, selectedItem, targetForm, targetControl, editorControls, ComboBox_SelectedIndexChanged, TextBoxControl_TextChanged, CheckBox_CheckChanged, NumericUpDown_ValueChanged, OnItemChecked, dateTimePicker_ValueChanged, liveLookupValidationFailed, populateComboBoxCallback);
            }
            else
            {
                targetControl.Padding = new Padding(2);
                targetControl.Controls.Clear();

                ...{other code doing stuff here }
            }
         }

在大约12个使用此代码的实例中,会引发跨线程异常。所有使用此代码的实例都是使用“await”关键字异步实现界面构建的。

根据GWLlosa的建议,我编写了一个扩展方法来获取控件所属的OwningThread:

    public static Thread OwnerThread(this Control ctrl)
    {
        Thread activeThread = null;

        if (ctrl.InvokeRequired)
        {
            activeThread = (Thread)ctrl.Invoke(new Func<Control, Thread>(OwnerThread), new object[] { ctrl });
        }
        else
        {
            activeThread = Thread.CurrentThread;
        }

        return activeThread;
    }

...这强调了在几次迭代后,线程ID确实发生了变化。

在某些代码的深处隐藏着一些用于获取数据并填充相关控件的例程,通过使用Task.Run(),来自MSDN(https://msdn.microsoft.com/zh-cn/library/hh195051(v=vs.110).aspx)中明确说明:

示例表明异步任务在与主应用程序线程不同的线程上执行

一旦将Task.Run()排除在外,控件的线程就不再改变。因此,在使用它时需要小心谨慎!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接