代码改变世界

C# 线程手册 第四章 线程设计原则 管道线程模型

2012-03-18 20:03  DanielWise  阅读(1828)  评论(1编辑  收藏  举报

  管道线程模型基于一系列任务,每个任务都依赖于前一个任务。我们看一下图 5 的描述:

PipeThread

图 5

  在上面的图片中,主线程创建了一系列线程,每个线程都要等待之前的线程完成以后才能执行。如果你的任务有特定阶段而且每个阶段都依赖其他阶段,那么这种线程关系是很适合的。例如,你的任务可能是处理输入数据的而且整个过程有一些子任务:

  1. 过滤所有的不合法字符, 比如<, >, ! 等等。

  2. 检查数据是否被正确地格式化了。

  3. 使用货币符号和十进制小数点对所有数字进行格式化。

  4. 处理输出。

这种情况下,只有上一个任务完成了以后下一个任务才可以开始。在上一篇讨论的模型中,按顺序执行就是我们实际上处理的,因为阶乘字段会在线程的最后被设置值。然而,实现管道线程模型最恰当的方式是显式检测线程是否已经结束,如果已经结束就可以继续下一个任务/线程。

  我们需要对上一篇的对等线程例子做一些修改。首先,让我们来看看frmCalculate 的代码:

/*************************************
/* 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.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Collections;

namespace MainWorker
{
    public partial class frmCalculator : Form
    {
        PipeLineThread threadMthods;
        Thread mCalculateFactorsThread;
        Thread mCalculateFactorialThread;
        delegate void UpdateValue(string text);

        public frmCalculator()
        {
            InitializeComponent();

            threadMthods = new PipeLineThread();         }

        private void cmdFactors_Click(object sender, EventArgs e)
        {
            Thread calculatorFactors = new Thread(new ThreadStart(FactorsThread));
            calculatorFactors.Start();
        }

        private 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);
        }

        private void NewFactorsThread()
        {
            ArrayList val = threadMthods.CalculatorFactors();
            mCalculateFactorialThread.Join();
            StringBuilder sb = new StringBuilder();
            for (int count = 0; count <= val.Count; 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);
        }

        private void cmdFactorials_Click(object sender, EventArgs e)
        {
            mCalculateFactorsThread = new Thread(new ThreadStart(NewFactorsThread));
            mCalculateFactorialThread = new Thread(new ThreadStart(FactorialThread));

            mCalculateFactorsThread.Start();
            mCalculateFactorialThread.Start();
        }

        private void FactorialThread()
        {
            long val = threadMthods.CalculatorFactorial(8);

            //Create and invoke the delegate with the new value
            UpdateValue updVal = new UpdateValue(DisplayValue);
            string[] args = { val.ToString() };
            this.Invoke(updVal, args);
        }

        void DisplayValue(string text)
        {
            lblResult.AppendText(text);
            lblResult.AppendText("\r\n");
        }
    }
}

  你可以看到改动很小。我们唯一感兴趣的地方是重定义了calculateFactorial 的作用范围和 calculateFactorial.Join()。这行代码告诉CalculateFactors 线程等待calculateFactorial 线程执行完。Join() 调用应该放在你想要暂停执行的线程内部,Join() 调用由需要等待的线程调用。这意味着calculateFactorial 线程变量的作用范围至少应该重定义为类成员以便于其他线程可以访问它。

/*************************************
/* 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;
using System.Threading;

namespace MainWorker
{
    public class PipeLineThread
    {
        private int factorial;

        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 ArrayList CalculatorFactors()
        {
            for (int count = 1; count <= 30; count++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(1));
                if (factorial > 0)
                {
                    break;
                }
                else if (count == 30 && factorial == 0)
                {
                    return null;
                }
            }
            ArrayList returnValue = CalculatorFactors(factorial);
            return returnValue;
        }

        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;
            }
        }
    }
}

  同样的,这里的改动也很小。上面的方法再也不用检查阶乘的值是否已经设置,同时能够继续执行因为它可以假设阶乘的值已经计算出来了。这个例子在以下情况时很有用,例如,你在等待填充一个DataSet 数据集,然后执行进一步的计算过程。当线程填充完数据集以后线程会立即触发事件。当然,在真实应用中,可能还需要实现错误检查因为线程可能会因为一个错误导致异常退出而非正常退出,或者另外一个线程调用Abort() 方法终止了当前线程执行。

  在管道线程模型下你要小心处理一些线程陷阱,这些陷阱在任何其他线程中都可能发生。第一个线程可能进入死循环,这种情况下,第二个线程永远都不会执行。通过保证死循环问题永远不会发生在第一个线程中,你就可以保证第二个线程能正常结束。还有,你要注意线程可能异常退出,由于一个错误或者其他原因。

  这里简要介绍了线程的三种模型。通过在程序中使用其中的任意一种模型,你应该对自己将要使用的代码结构更加熟悉一些。

 

总结

  在这一章,我们介绍了在程序中可以使用的几种线程模型,以及为什么你应该使用线程模型和如何实现这些模型。现在你应该对以下知识比较自信:

  1. 不同类型的基础线程模型, 比如STA 和 MTA 单元。

  2. 如何确定线程模型。

  3. 可以使用的不同线程模型以及它们彼此之间的关系

  这一章应该能够帮助你设计自己的多线程应用程序 - 你的应用程序应该符合上面描述的多种线程模型之一,同时上面的例子应该可以帮助你实现自己的代码。通过.NET Framework , 线程已经足够强大, 同时相比之前也更容易出错。如果知道你的程序属于哪种模型,你就能够注意到自己的程序中可能有的潜在错误。

 

下一篇介绍第五章 扩展多线程应用程序…