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

发布于 2024-10-08 07:39:03 字数 1676 浏览 1 评论 0原文

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

我的表单中有一个方法:

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 分支上的方法内,我得到了提到的异常:
跨线程操作无效:控制“txtNumber”是从创建它的线程以外的线程访问的。

我的场景实际上有点复杂 - 我只是以此作为示例进行推断。实际情况是,我正在使用 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 为 false!怎么会!?当然,后来它无法更改子控件(这并不奇怪)。难道表单以某种方式改变了它所在的线程?

编辑2 现在我正在调用:

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

并且它的 IsHandleCreated 为 true,但仍然因 devSpeed 指向,否则不能在控件上调用。

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

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

I have a form with my custom controls on it.

I have a method in my Form:

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...
  }
}

And inside the methods that are on the else branch I get the mentioned exception:
Cross-thread operation not valid: Control 'txtNumber' accessed from a thread other than the thread it was created on.

My scenario is actually a bit more complicated - I just extrapolated this as an example. What's actually going on is that I'm using WorkflowFoundation - I have StateMachineActivity (CTP1) running in WorkflowApplication (which runs in it's own thread), I subscribed to it's event, and from there I'm calling SetEnabledOnControls. Also, I'm using bookmarks to resume my workflow (and also, there's MEF on the side, not involved in the scenario).

All of that is irrelevant to my obvious misunderstanding of the InvokeRequired - how is it possible that if the InvokeRequired is false, I have cross threaded exception? I don't create any of the controls 'manually' - it's all there in the Initialize() placed by designer.

Can anyone shed some light on this?

Thanks!

EDIT
Using GWLlosa suggestion, I've tracked the ThreadId using System.Threading.Thread.CurrentThread.ManagedThreadId. Now comes the weird part... the thread id in Initialize() is 10. Between passing the first 2 states, it comes in with Id 13 - InvokeRequired was true, and it invoked correctly. BUT, after the second state, when it enters SetEnabledOnControls it's again 13, but this time InvokeRequired is false! How come!? Later on, of course, it fails to change child controls (not surprising). Could it be that the Form somehow changed the thread it's living in??

EDIT 2
Now I'm calling with:

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

and it has IsHandleCreated to true, but still fails with what devSpeed pointed at.

EDIT 3
FACEPALM :) One of the buttons that were resuming state was at first CancelButton for the Form. When it was removed from the property as such, the codebihind still had the DialogResult=Cancel for it - so my form was indeed closing, and of course it was missing the handle so the InvokeRequired didn't return correct info, and hence the errors.

Thanks everyone! I learned a new thing today :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

〗斷ホ乔殘χμё〖 2024-10-15 07:39:03

如果在创建控件时(在 Initialize() 函数中)记录线程 ID 以及在尝试触摸控件之前记录线程 ID,可能会使调试更加容易。一般来说,当您以某种方式在线程上创建控件而不是您最初期望的线程时,我就看到过这种情况发生。

It might make your debugging easier if you log the thread ID when the controls are created (in the Initialize() function) and the thread ID just before you try to touch it. Generally, I've seen this happen when you've somehow created the controls on a thread other than the one you expect in the first place.

墨小墨 2024-10-15 07:39:03

我自己也遇到了类似的问题,我拆除了一个函数,然后在 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(),控件的线程就永远不会改变。因此,您需要谨慎使用它的方式和时间!

I just ran into a similar problem myself, where I have a function tearing down, and then dynamically creating controls within a 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 }
            }
         }

And in one instance of about 12 where this code was being used, a cross-thread exception was being raised. All instances of where this code was being used, was written in such a way, that the interface building is achieved asynchronously using the 'await' keyword.

Using the suggestion made by GWLlosa, I wrote an extension method to controls to get the OwningThread a control belongs to:

    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;
    }

..which highlighted that after a few iterations the Thread Id was indeed changing.

Buried deep inside some of this code were routines used to fetch data populating the relevant controls, through the use of Task.Run() which from MSDN (https://msdn.microsoft.com/en-us/library/hh195051(v=vs.110).aspx) clearly states:

The example shows that the asynchronous task executes on a different
thread than the main application thread

Once the Task.Run() was taken out of the equation, the control's thread never changed. So you need be careful about how and when you use it!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文