Visual C#中使用线程(3)

发表于:2007-06-30来源:作者:点击数: 标签:
主窗体 下一步是向窗体添加代码以生成辅助线程并针对各辅助线程启动 MQListen 类。首先,请向窗体添加下列函数: // C# private void StartThreads() { int LoopCounter; // 线程计数 StopListeningFlag = false; // 跟踪辅助线程是否应当 // 终止的标志。 /
     主窗体
    下一步是向窗体添加代码以生成辅助线程并针对各辅助线程启动 MQListen 类。首先,请向窗体添加下列函数:
  
  // C#
  private void StartThreads()
  {
  int LoopCounter; // 线程计数
  StopListeningFlag = false; // 跟踪辅助线程是否应当
  // 终止的标志。
  
  // 将一个包含 5 个线程的数组声明为辅助线程。
  Thread[] ThreadArray = new Thread[5];
  
  // 声明包含辅助线程的所有代码的类。
  MQListen objMQListen = new
  MQListen(this.ServerName.Text,this.QueueName.Text);
  
  for (LoopCounter = 0; LoopCounter < NUMBER_THREADS; LoopCounter++)
  {
  // 创建一个 Thread 对象。
  ThreadArray[LoopCounter] = new Thread(new
  ThreadStart(objMQListen.Listen));
  // 启动线程将调用 ThreadStart 委托。
  ThreadArray[LoopCounter].Start();
  }
  
  statusBar1.Text = LoopCounter.ToString() + " listener threads started";
  
  while (!StopListeningFlag)
  {
  // 等待用户按下停止按钮。
  // 在等待过程中,让系统处理其他事件。
  System.Windows.Forms.Application.DoEvents();
  }
  
  statusBar1.Text = "Stop request received, stopping threads";
  // 向每个线程发送一个中断请求。
  for (LoopCounter = 0;LoopCounter < NUMBER_THREADS; LoopCounter++)
  {
  ThreadArray[LoopCounter].Interrupt();
  }
  
  statusBar1.Text = "All Threads have been stopped";
  }
  
  
    代码讨论
  
    要启动此函数,请创建一个包含 5 个项目的线程数组。此数组将保持对所有线程的引用,以备将来使用。
  
    MQListen 类的构造函数使用两个参数:包含消息队列的计算机名以及要侦听的队列的名称。构造函数使用文本框中的值来为这两个参数赋值。
  
    要创建线程,您需要进入循环以初始化每个线程对象。Thread 构造函数要求您向其传递一个委托,该委托在调用线程的 Start 方法时指向要调用的函数。您希望线程开始使用 MQListen.Listen 函数,但该线程并不是一个委托。为了满足线程构造函数的要求,您必须传递一个 ThreadStart 对象,该对象将创建一个给定函数名称的委托。此时,请向 ThreadStart 对象传递一个对 MQListen.Listen 函数的引用。由于该数组元素已被初始化,请立即调用 Start 来开始线程。
  
    所有线程开始后,请用相应的消息来更新窗体中的状态栏。随着线程的运行和侦听队列,主线程将等待用户请求应用程序停止侦听。为此,主线程将进入一个 while 循环,直至您单击 StopListening 按钮更改 StopListeningFlag 的值。在此等待循环中,将允许应用程序使用 Forms.Application.DoEvents 方法处理其他需要处理的工作。对于熟悉 Visual Basic 的读者来说,这一点与旧的 DoEvents 方法相同。对于熟悉 C++ 的读者来说,这等于编写一个 MSG 泵。
  
    当用户单击 StopListening 按钮时,该循环将退出并进入线程关闭代码。要关闭所有线程,代码必须检查线程数组,并向每个线程发送一个中断信号。在此循环内部,请对数组中的每个线程调用 Interrupt 方法。调用此方法之前,MQListen 类中的代码将继续正常执行。因此,您可以对每个辅助线程调用 Interrupt,而不必考虑线程是否正在处理其他事件。完成后,线程类将处理所有线程的清除。最后,请在退出前更新主窗体中的状态栏。
  
    现在,您需要在按钮后添加代码。请向 StartListening 按钮的 Click 事件添加以下代码:
  
  
    // C#
  
    statusBar1.Text = "Starting Threads";
  
    StartThreads();
  
    这将更新状态栏并调用 StartThreads 方法。对于 StopListening 按钮,您只需使用以下代码将 StopListeningFlag 设置为 True:
  
  
    // C#
  
    StopListeningFlag = true;
  
     最后一步是为 StopListeningFlag 添加窗体级的变量。请在窗体代码的顶部添加以下行:
  
    // C#
  
    private bool StopListeningFlag = false;
  
    要测试应用程序,您可以下载 MQWrite,这是一个写入消息队列的示例应用程序。
  
    多线程代码问题
    您已经完成了示例代码,因此您已经具备编写自己的多线程应用程序所需的工具。线程可以显著提高某些应用程序的性能和可伸缩性。在功能增强的同时,您还必须了解线程有危险的一面。使用线程可能会破坏您的应用程序,这样的情况确实存在。线程可能会阻止运行,造成无法预料的后果,甚至会导致应用程序停止运行。
  
    如果您有多个线程,请确保它们之间不存在互相等待以到达某一点或完成的情况。如果操作错误,可能会导致死锁状态,两个线程都无法完成,因为它们都在相互等待。
  
    如果多线程要求访问不能轻易共享的资源(如软盘驱动器、串行端口或红外线端口),您可能需要避免使用线程或需要使用一种更高级的线程工具(如 synclocks 或 mutexes)来管理并发性。如果两个线程试图同时访问这些资源,其中一个线程将无法获得资源,或者会导致数据损坏。
  
    使用线程的另一个常见问题是竞争状态。如果一个线程正在将数据写入文件,而另一个线程正在从该文件中读取数据,您将无法知道哪个线程先完成。这种情况称为竞争状态,因为两个线程都在竞相到达文件末尾。如果读取线程快于写入线程,则将返回无法预料的结果。
  
    使用线程时,还应当考虑所有线程是否都能够完全独立地进行工作。如果确实需要来回传递数据,在数据相对简单的情况下,只要小心操作即可。传递复杂对象时,来回移动这些对象的封送代价将十分可观。这将导致操作系统管理的额外开销并且会降低总体性能。
  
    另一个问题是将代码转交给其他开发人员的传递成本。虽然 .NET 确实使线程变得容易,但请注意,维护您代码的下一位开发人员必须了解要使用的线程。尽管这不是避免使用线程的理由,但是它充分说明了应该提供足够的代码注释。
  
    这些问题本身并不能打消您使用线程的热情,但您在设计应用程序和决定是否使用线程时应该考虑到这些问题。遗憾的是,本文无法详细论述某些避免这些问题的方法。如果您已决定使用线程但遇到了上述某些问题,请检查 synclocks 或 mutexes 看是否能解决问题或引导您使用其他解决方案
  
    总结
    有了上述信息,您就可以编写使用线程的应用程序。不过,在编写过程中,请记住上面提到的问题。如果使用得当,那么,与单线程相比,多线程应用程序将具有更好的性能和可伸缩性。但是,如果使用不当,使用线程会适得其反,并且会导致应用程序不稳定。
  
  

原文转自:http://www.ltesting.net