在 C# 中,编写多线程GUI的另一

分享于 

6分钟阅读

Web开发

  繁體
Screenshot - GUIThreads1.JPG

介绍

本文概述了在 C# 中编写响应式多线程 Windows 表单GUI的另一种方法。 我说"替换"是一种不遵循当前that令牌的技术,只有创建GUI控件的线程应该与它交互。 只有当GUI中的一个或者多个控件处理数十或者数百条消息时,才应该考虑这种技术,从而导致GUI变得无React。 通常情况下,使用实时数据源时是 true。 应该只读取问题的控件,而不是通过GUI进行更新。 在所有其他情况下,应该使用标准 BeginInvoke/SycnchronizationContext/AsyncOperation 调用。 对于大多数 Windows 表单gui来说,这种技术并不合适。

背景

多年前,当我在写实时C Windows 应用程序时,将几个线程与应用程序 Windows的几个子程序关联是不常见的。 这些年来,这种技术已经被使用了较少的( 并记录)。 最初,微软文档化并鼓励开发人员使用这种技术。 今天,这项技术已经成为完全。 现在,当我与它的他. NET 开发人员讨论这种技术时,它们甚至不会相信它会工作。 通过本文,我打算说明在某些情况下,从其他线程更新GUI控件是完全正确的。

示例代码

样例应用程序是一个非常基本的proof-of-concept框架。 它不包含验证或者异常处理代码。 显然,在生产代码中不存在这种情况。 示例代码的第一件事是创建要与表单控件的选定主子进行关联并运行的线程。 这是在窗体事件处理程序的主 Load 中完成的。

privatevoid theMainForm_Load(object sender, EventArgs e)
{
 // Prevent the framework from checking what thread the GUI is updated from. theMainForm.CheckForIllegalCrossThreadCalls = false;
 // Create our worker threads and name them.// The name will be used to associate a thread with a specific ListView worker1 = new Thread(new ThreadStart(UpdateListView));
 worker1.Name = "Worker1";
 worker2 = new Thread(new ThreadStart(UpdateListView));
 worker2.Name = "Worker2";
 worker3 = new Thread(new ThreadStart(UpdateListView));
 worker3.Name = "Worker3";
 worker4 = new Thread(new ThreadStart(UpdateListView));
 worker4.Name = "Worker4";
 // Get all the threads running. Start();
}

注意上代码中,我们将表单属性的CheckForIllegalCrossThreadCalls 设置为 false。 这里属性已经在. NET 2.0中添加。 如果没有设置为 false,框架将在运行时引发 InvalidOperationException,如下所示:

Screenshot - GUIThreads2.JPG

线程执行的UpdateListView 方法只是填充并清空它关联的List 视图,直到被告知停止。 在更真实的场景中,线程可能会等待同步对象。 对象发出信号时,线程可能会处理与该控件关联的工作队列中的任何项。 下面是 UpdateListView 方法:

privatevoid UpdateListView()
{
 ListView lv = null;
 ListViewItem item = null;
 string name = Thread.CurrentThread.Name;
 int loopFor = 20;
 int sleepFor = 25;
 int count = 0;
 switch (name)
 {
 case"Worker1":
 lv = listView1;
 break;
 case"Worker2":
 lv = listView2;
 break;
 case"Worker3":
 lv = listView3;
 break;
 case"Worker4":
 lv = listView4;
 break;
 }
 // Keep running until we're told to stop.while (run)
 {
 // Add n items to the list.for (int i = 0; i < loopFor; ++i)
 {
 item = new ListViewItem(DateTime.Now.ToString("HH:mm:ss.ffff"));
 item.SubItems.Add(string.Format("{0}: item {1}", name, ++count));
 lv.Items.Insert(0,item);
 Thread.Sleep(sleepFor);
 }
 // Now remove them.for (int i = 0; i < loopFor; ++i)
 {
 lv.Items.RemoveAt(0);
 Thread.Sleep(sleepFor);
 }
 }
}

运行样例应用程序

当你启动示例应用程序时,四个ListView控件将立即开始填充文本,然后清空。 当发生这种情况时,尝试调整窗体大小。 更新状态栏文本或者从主菜单显示关于对话框的模式。 将ListViews列标题拖动到这里,以便对它的进行排序。 单击列标题几次以按升序或者降序对列进行排序。 这都是安全的。

尽管ListView控件中的所有活动都有React,但应用程序感觉如何响应。 这是由于所有繁重工作都是通过与ListView控件关联的4 个线程在后台完成的。 表单中的所有其他控件都通过主GUI线程。 在关闭应用程序之前,必须按下"结束循环"按钮,以终止四个与四个ListView控件关联的线程。

效率

如果需要注意,仅允许与特定控件关联的线程更新它的内容( 它的项集合或者数据源),这种技术可以产生非常快速。 在简化代码时,可以看到额外的好处,因为不需要调用 InvokeRequired()BeginInvoke() 或者 EndInvoke()。 此外,将调用封送到GUI线程的性能开销也已经被移除。 如果 Windows 表单GUI中的速度是最高优先级,那么你可能需要尝试这个技巧。

杂项便笺

请记住,因为这是一个不经常看到的技术,所以请确保你的代码被重新注释以保护。 这里代码是使用 Visual Studio 2005开发的。

历史记录

  • 7 2007年12月 --原始版本已经发布
  • 18 2007年12月 --更新示例代码和文本以阐明我的意图

相关文章