一个多线程应用程序中的线程彼此间可能关联也可能不关联。例如,在每个程序中都有一个用来生成其他子线程的主线程,所以主线程就成了所有其他线程的控制器。在一个多线程应用程序中有三种常用方法来定义线程间的关系:
1. 主线程和工作线程模型
2. 对等线程模型
3. 管道线程模型
我们将详细讨论每一个模型,借助一些代码来使你能够知道如何在自己的程序中实现它们。
主线程和工作线程模型
这是最常见的线程模型也是到目前为止本书一直使用的模型。如图3表示:
图 3
在主线程和工作线程模型中,主线程接收所有的输入并把输入参数传递给其他线程以便于执行一些特定的任务。主线程可以等待/不等待工作线程完成。在这个模型中,工作线程不直接与输入资源打交道,而是通过主线程作为中转。例如,我们有一个Windows 窗体程序,上面有三个按钮分别触发三个不同事件:
1. 从一个web service 获得数据
2. 从一个数据库获得数据
3. 做一些诸如截取XML文件的任务
这是最简单的线程模型。主线程包含在Main() 方法中,这个模型在客户端界面程序中非常常见。
让我们看一些代码以更好地描述这个问题。现在看一下一个窗体程序:
当你单击一个按钮,它将触发一个执行一些运算的工作线程并在按钮下面的空白处显示返回结果。我们不对界面做详细讨论;这里只给出部分代码,全部代码请从这里下载。
/************************************* /* Copyright (c) 2012 Daniel Dong * * Author:oDaniel Dong * Blog:o www.cnblogs.com/danielWise * Email:o guofoo@163.com * */ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Collections; namespace MainWorker { public class MainWorker { public ArrayList CalculatorFactors(double number) { if (number < 3) { return null; } else { ArrayList factors = new ArrayList(); factors.Add("1"); for (double current = 2; current <= number - 1; current++) { if ((double)(Math.Floor(number / current) * current) == number) { factors.Add(current.ToString()); } } factors.Add(number.ToString()); return factors; } } public long CalculatorFactorial(int number) { if(number < 0) { return -1; } if (number == 0) { return 1; } else { long returnValue = 1; for (int current = 1; current <= number; current++) { returnValue *= current; } return returnValue; } } } }
上面的代码非常易于理解而且为了模块化的原因被包装到一个类中。第一个方法返回一个包含所有传递给它的数的阶乘的ArrayList, 而第二个方法简单地返回一个长整型数字。记住阶乘的变化是非常快的。13的阶乘是6,227,020,800. 计算阶乘的方法没有占用处理器很长时间,但是它可以用来描述这个模型。
public partial class frmCalculator : Form { MainWorker threadMthods; delegate void UpdateValue(string text); public frmCalculator() { InitializeComponent(); threadMthods = new MainWorker(); }
构造函数实例化了一个MainWorker对象。下面我们展示按钮事件处理方法的代码:
private void cmdFactors_Click(object sender, EventArgs e) { Thread calculatorFactors = new Thread(new ThreadStart(FactorsThread)); calculatorFactors.Start(); } void FactorsThread() { ArrayList val = threadMthods.CalculatorFactors(200); StringBuilder sb = new StringBuilder(); for (int count = 0; count <= val.Count - 1; count++) { sb.Append((string)val[count]); if (count < val.Count - 1) { sb.Append(","); } } //Create and invoke the delegate with the new value UpdateValue updVal = new UpdateValue(DisplayValue); string[] args = { sb.ToString() }; this.Invoke(updVal, args); }
cmdFactors_Click() 方法使用FactorsThread()方法实例化一个新的线程,这个线程会对MainWorker.CalculatorFactors() 方法返回值进行格式化处理。
这个方法需要包装起来因为线程方法不可以有返回值。
private void cmdFactorials_Click(object sender, EventArgs e) { Thread calculatorFactorial = new Thread(new ThreadStart(FactorialThread)); calculatorFactorial.Start(); } void FactorialThread() { long val = threadMthods.CalculatorFactorial(20); //Create and invoke the delegate with the new value UpdateValue updVal = new UpdateValue(DisplayValue); string[] args = { val.ToString() }; this.Invoke(updVal, args); }
FactorialThread() 方法就更加简单一些。不论何时cmdFactorial 按钮被点击,主线程都会触发一个新线程并会等待结果准备好以后更新lblResult 文本框。
这是一个关于主线程和工作线程的非常简单的例子。显然这个例子可以很容易的改变成处理数据库的连接的方法,或者其他更耗时的活动。
然而,当你使用这个模型时你应该小心处理几个线程相关的问题。你可能使用线程生成线程,并让线程访问同一资源,最终导致线程进入无限循环状态。
这是最简单的模型,但是也是需要程序员做最多工作的模型。
还有,在这个模型中各个线程间相互独立,每个线程都由其父线程控制 - 这个例子中就是主线程。
下一篇将介绍对等线程模型...