科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件从桌面移动到设备:多线程和用户界面(3)

从桌面移动到设备:多线程和用户界面(3)

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

本文帮助开发人员克服他们在使用后台线程与用户界面进行交互时Microsoft .NET Compact Framework的局限性,具体内容包括:多线程和用户界面基础知识和构建更好的类。

作者:Jim Wilson 来源:51CTO.com 2007年9月1日

关键字:

  • 评论
  • 分享微博
  • 分享邮件

异步执行

另一个主要不同点是,台式计算机支持 Control.BeginInvoke,这样委托可以异步执行。在我们的应用程序中,每次调用 lbData.Invoke 时,后台线程就挂起执行,直到 AddItem 方法结束。结果是,循环的每次迭代中应用程序被迫导致一个线程上下文切换。

一般情况下,我们希望将线程上下文切换降低到最低限度,因为执行它的成本相当高;首选的做法是允许操作系统选择何时发出线程上下文切换。用 .NET Framework 中的 Control.BeginInvoke 替代 Control.Invoke 调用消除了这种不得已的线程上下文切换,并允许后台线程继续处理,直到操作系统决定执行一个线程上下文切换并运行委托。

为了更新 Work1_ 方法来异步运行 AddItem 委托,我们只需使用 lbData.BeginInvoke 替代对 lbData.Invoke 的调用。

lbData.BeginInvoke(eh, new object[]{line, EventArgs.Empty});

构建更好的类

我们在构建多线程设备应用程序时,.NET Compact Framework Control 类缺乏支持传递参数以及异步执行增加了复杂性并降低了效率。我发现这种不支持是一个特别重要的问题,因为智能设备应用程序一般都使用多线程。同时,智能设备的资源往往有限,这使得简单、有效的多线程非常重要。

因为我们没有使用 .NET Compact Framework 源代码,因此我们不能合理地向 .NET Compact Framework Control 类添加对参数和异步委托执行的支持。但是,我们可以构建一个提供这些功能的新类。我将该类称为 UISafeInvoker。

一言以蔽之,UISafeInvoker 是一个与线程有关的 .NET Compact Framework 类,它提供了行为与 .NET Framework Control 类的 Invoke 与 BeginInvoke 方法类似的 Invoke 与 BeginInvoke 方法。虽然不是 Control 类的一部分,但 UISafeInvoker.Invoke 与 UISafeInvoker.BeginInvoke 方法的使用却非常简单。

与可以执行任何种类委托的台式机的 Control.Invoke 与 BeginInvoke 方法不同,UISafeInvoker 和 .NET Compact Framework Control.Invoke 与 BeginInvoke 方法一样,只支持 EventHandler 委托。因为 UISafeInvoker 仅支持一种委托类型,因此不需要使用对象数组来传递参数。相反,Invoke 与 BeginInvoke 接受直接传递给 EventHandler 委托中对应参数的对象和 EventArgs 参数。这里是每种方法的签名。

void Invoke(EventHandler eh, object obj, EventArgs eArgs); IAsyncResult BeginInvoke(EventHandler eh, object obj, EventArgs eArgs);

使用 UISafeInvoker 就是简单地在 Form 类中声明一个引用,并在 Form 构造函数中创建一个实例。创建之后,UISafeInvoker 就内部跟踪创建它的线程,因此 Invoke 和 BeginInvoke 方法可以在同一个线程上运行期望的委托。作为 Form 构造函数的一部分而创建 UISafeInvoker,并且是在与所有窗体控件相同的线程上创建;因此,Invoke 或者 BeginInvoke 方法运行的任何委托都可以安全地更新 UI 控件。

这里是使用 UISafeInvoker 更新后的测试应用程序。

class MyForm : Form{ ListBox lbData ; UISafeInvoker invoker ; // Declare UISafeInvoker MyForm() { InitializeComponent(); invoker = new UISafeInvoker(); // Create UISafeInvoker on main UI thread Thread t = new Thread(new ThreadStart(Work1_)); t.Start() ; // Runs Work1_ on a background thread } void Work1_(){ // Wrap AddItem in delegate EventHandler eh = new EventHandler(AddItem); StreamReader rdr1 = new StreamReader(@"\My Documents\DataFile.dat"); string line = rdr1.ReadLine(); while(line != null) { invoker.BeginInvoke(eh, line, EventArgs.Empty); // Pass to AddItem line = rdr1.ReadLine(); } } // o receives the reference to line, e receives EventArgs.Empty void AddItem(object o, EventArgs e) { string line = (string) o; // Upcast o lbData.Items.Add(line); // Add to Listbox } }

对于 UISafeInvoker,我们的 .NET Compact Framework 应用程序已经克服了 .NET Compact Framework Control.Invoke 方法的局限性,现在可以提供简单、有效的线程内通信了。这种通信类似于 .NET Framework 中的通信,不需要额外的数据结构或者复杂的编码。

使用窗口消息

在内部,UISafeInvoker 非常简单,因为它仅完成两件事情:跟踪创建它的线程并提供一种在线程之间传输数据的可靠方法。

这种解决方案 — 虽然听起来有些陈旧 — 是基于窗口消息的。Windows 操作系统创建的所有窗口都有一个消息队列。应用程序可以通过使用 Microsoft Win32_ SDK 函数 SendMessage 和 PostMessage 将消息放置到该队列中。这些函数允许应用程序传递一个标识执行操作的整数标志和两个过去被称为 WParam 与 LParam 的消息定义数据值。

除了有一个主要的不同点,SendMessage 和 PostMessage 函数基本上是一样的。SendMessage 将消息放置到窗口消息队列中,并阻止消息直到窗口完成对它的处理。PostMessage 将消息放置到窗口消息队列中并立即返回。SendMessage 和 PostMessage 可以由任何线程调用,但是窗口总是在创建该窗口的线程上处理消息。这种行为隐含地解决了线程跟踪和在线程间传输数据的问题。

所有的 UI 控件都是窗口,但是应用程序也可以创建隐藏的窗口,它们能够处理消息,但在屏幕上不会呈现任何可见物。MessageWindow 类向 .NET Compact Framework 公开了实现隐藏窗口的功能。

从根本上说,UISafeInvoker 只是封装了一个隐藏窗口和对 SendMessage 和 PostMessage 的调用。这里是UISafeInvoker 实现的框架。

public class UISafeInvoker : MessageWindow { const int WM_USER = 1024; // Traditional start of application-defined messages const int WM_INVOKEMETHOD = WM_USER + 1; // Our special message // Handle window message processing protected override void WndProc(ref Message m) { base.WndProc (ref m); if (m.Msg == WM_INVOKEMETHOD) { // Get data from message // Run delegate } } // Instigate delegate execution and wait for completion public void Invoke(EventHandler eh, ...) { Message m = Message.Create(this.Hwnd, WM_INVOKEMETHOD, ...); MessageWindow.SendMessage(ref m); } // Instigate delegate execution and return immediately public void BeginInvoke(EventHandler eh, ...) { Message m = Message.Create(this.Hwnd, WM_INVOKEMETHOD, ...); MessageWindow.PostMessage(ref m); } }

Invoke 和 BeginInvoke 方法都向包含有关运行委托信息的隐藏窗口发送消息。由于 Invoke 方法使用 SendMessage ,因此是同步运行。BeginInvoke 提供异步执行,因为 PostMessage 将消息放置到窗口消息队列中并立即返回。

每次隐藏窗口接收到一条消息就会调用 WndProc 方法,它负责运行期望的委托。由于它是作为窗口消息处理的一部分而调用的,因此创建该窗口的线程总是执行 WndProc 方法。因为总是在同一个线程上运行代码,所以它运行的委托可以安全地与在同一个线程上创建的 UI 控件进行交互。

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章