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

时间:2010-12-16 17:58:48

标签: c# winforms .net-4.0 thread-safety multithreading

我有一个带有自定义控件的表单。

我的表格中有一个方法:

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跟踪了ThreadId。现在来了奇怪的部分...... Initialize()中的线程id是10.在传递前2个状态之间,它带有Id 13 - InvokeRequired为true,并且它被正确调用。但是,在第二个状态之后,当它进入SetEnabledOnControls时它又是13,但这次InvokeRequired是假的!怎么会!?当然,后来它无法改变儿童控制(这并不奇怪)。可能是Form以某种方式改变了它所生活的线程吗?

编辑2 现在我打电话给:

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

并且IsHandleCreated为true,但仍然无法使用devSpeed pointed at

编辑3 FACEPALM :)其中一个恢复状态的按钮首先是Form的CancelButton。当它从属性中删除时,codebihind仍然有DialogResult =取消它 - 所以我的表单确实关闭了,当然它缺少句柄所以InvokeRequired没有返回正确的信息,因此错误

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

2 个答案:

答案 0 :(得分:2)

如果在创建控件时(在Initialize()函数中)记录线程ID,并在尝试触摸它之前记录线程ID,则可能使调试更容易。一般来说,我已经看到这种情况发生在你以某种方式在一个线程上创建控件而不是你期望的那个线程。

答案 1 :(得分: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确实在变化。

通过使用来自MSDN(https://msdn.microsoft.com/en-us/library/hh195051(v=vs.110).aspx)的Task.Run()清楚地说明:

,其中一些代码是用于获取填充相关控件的数据的例程。
  

该示例显示异步任务在不同的上执行   线程比主应用程序线程

一旦Task.Run()从等式中取出,控件的线程就不会改变。因此,您需要注意使用它的方式和时间!