首页 文章

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

提问于
浏览
6

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

我的表格中有一个方法:

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.

我的场景实际上有点复杂 - 我只是将其推断为一个例子 . 什么's actually going on is that I' m使用WorkflowFoundation - 我在WorkflowApplication中运行StateMachineActivity(CTP1)(在其中运行's own thread), I subscribed to it' s事件,从那里我调用 SetEnabledOnControls . 另外,我是侧面的MEF,没有参与场景) .

所有这些都与我对InvokeRequired的明显误解无关 - 如果InvokeRequired为false,我有可能有交叉线程异常吗?我没有“手动”创建任何控件 - 它都在设计者放置的Initialize()中 .

任何人都可以对此有所了解吗?

谢谢!

EDIT 使用GWLlosa建议,我使用 System.Threading.Thread.CurrentThread.ManagedThreadId 跟踪了ThreadId . 现在来了奇怪的部分...... Initialize()中的线程id是10.在传递前2个状态之间,它带有Id 13 - InvokeRequired为true,并且它被正确调用 . 但是,在第二个州之后,当它进入 SetEnabledOnControls 时,它生活在?

EDIT 2 现在我打电话给:

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

它有 IsHandleCreated 为真,但仍然失败了什么devSpeed pointed at .

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

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

3 回答

  • 2

    也许你遇到了this problem .

  • 2

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

  • 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()来深入内部的一些代码是用于获取填充相关控件的数据的例程:

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

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

相关问题