代码之家  ›  专栏  ›  技术社区  ›  Unsliced

奇怪的跨线程用户界面错误

  •  6
  • Unsliced  · 技术社区  · 16 年前

    我正在编写一个WinForms应用程序,它有两种模式:控制台或GUI。同一解决方案中的三个项目,一个用于控制台应用程序,一个用于UI表单,第三个用于保存两个接口也将连接的逻辑。控制台应用程序运行非常顺利。

    包含用户选择的模型,它具有 IList<T> 其中t是局部对象, Step ,实现 INotifyPropertyChanged ,因此在UI中,它被装载到DataGridView上。运行时一切正常,对象的初始状态会反映在屏幕上。

    每一个 步骤 对象是一项依次执行的任务;某些属性将更改,并反射回IList并传递给DataGridView。

    在UI版本中,此操作是通过创建一个后台工作者将事件引发回UI来完成的。这个 步骤 它能产生一个 StepResult 对象,它是一个枚举类型,指示一个结果(例如running、notrun、ok、not ok、caveat)和一个字符串,用于指示一条消息(因为该步骤已运行,但与预期不完全一样,即带有caveat)。通常,这些操作将涉及数据库交互,但在调试模式下,我会随机生成一个结果。

    如果消息为空,则不会出现问题,但如果生成这样的响应:

    StepResult returnvalue = new StepResult(stat, "completed with caveat")
    

    我收到一个错误消息,说正在从创建它的线程以外的线程访问DataGridView。(我正在通过一个定制的处理程序传递这个消息,该处理程序应该在需要时处理调用——也许它不处理?)

    然后,如果我生成一个唯一的响应,例如使用随机数 r :

    StepResult returnvalue = new StepResult(stat, r.ToString());
    

    操作成功,没有问题,数字被清晰地写入DataGridView。

    我很困惑。我假设这是一个字面上的问题,但有人能给出更清楚的解释吗?

    5 回复  |  直到 12 年前
        1
  •  3
  •   Marc Gravell    16 年前

    因为您是通过事件订阅进行UI绑定的, you might find this helpful ;这是我不久前写的一个示例,演示了如何子类化 BindingList<T> 以便通知被自动编组到UI线程。

    如果没有同步上下文(即控制台模式),那么它将恢复为简单的直接调用,因此没有开销。在UI线程中运行时,请注意,这基本上使用 Control.Invoke ,如果委托在UI线程上,它本身就直接运行委托。因此,只有在从非UI线程编辑数据时才有任何开关-根据我们的需要进行调整;-p

        2
  •  4
  •   Tim Cooper    12 年前

    你已经回答了自己的问题:

    我收到一个错误消息,说正在从创建它的线程以外的线程访问DataGridView。

    WinForms坚持在窗体和控件上执行的所有操作都是在创建窗体的线程的上下文中完成的。原因很复杂,但与底层的win32 api有很大关系。有关详细信息,请参见 The Old New Thing 博客。

    您需要做的是使用invokeRequired和invoke方法来确保始终从同一线程访问控件(伪代码):

    object Form.SomeFunction (args)
    {
      if (InvokeRequired)
      {
        return Invoke (new delegate (Form.Somefunction), args);
      }
      else
      {
        return result_of_some_action;
      }
    }
    
        3
  •  0
  •   Matthew Kruskamp    16 年前

    我以前也有过同样的问题。也许我发表的这篇文章能有所帮助。

    http://cyberkruz.vox.com/library/post/net-problem-async-and-windows-forms.html

        4
  •  0
  •   Unsliced    16 年前

    我发现这篇文章- Updating IBindingList from different thread “—把责任指向绑定列表-

    因为没有为异步操作设置bindingList,所以必须从控制它的线程更新bindingList。

    将父窗体显式传递为 ISynchronizeInvoke 对象并为 BindingList<T> 做了这个把戏。