代码改变世界

C# 线程手册 第四章 线程设计原则 对等线程模型

2012-03-12 21:22  DanielWise  阅读(1244)  评论(0编辑  收藏  举报

  我们将要描述的下一个线程模型是对等线程模型。在这个线程模型中,每个线程都会从合适的源接收它自己的输入并对应地处理。这个模型在图4中做了描述。

2012-3-12 20-21-36

图 4

  在上面的图片中,UI 线程将根据键盘和鼠标的输入进行工作。工作线程A 将监听一个特定的套接字并处理来自套接字的输入,工作线程B将以同样的方式等待一个事件并在事件发生后对应处理。在这个模型中,所有线程都并发执行,不会阻塞或者等待其他线程。

  我们修改之前的例子以便于当计算阶乘的线程完成后可以通知CalculateFactors() 方法并找出特定数字的因数。在这个例子中我们使用8作为阶乘数,不会使用套接字,但是会通过一个变量设置来模型。这个原理与套接字是一样的;你可以继续监听套接字或者睡眠来节省处理器周期。

  好的,让我们先来改一下WorkerThread 类:

/*************************************
/* 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 PeerThread
    {
        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;
            }
        }
    }
}

  首先,我们将解释下这个小改动。使用一个私有字段来存储阶乘结果。还有,这个类被重命名为PeerThread. CalculatorFactors() 方法现在有一个重载方法,以便于在没有给它传递参数时仍然可以走执行这个模型的商业逻辑。

  线程要做的事情就是监控factorial字段的状态,假设这个字段是一个套接字对象,它会检测来看套接字接口是否有数据,如果有数据的话,线程会调用CalculatorFactors() 方法并将factorial 作为参数传递给CalculatorFactors() 方法,然后返回一个ArrayList. 我们已经做了一些改动---在CalculatorFactors() 方法开始会将factorial 字段重置。在方法的最后,我们将这个值恢复。

  现在还需要改一下frmCalculate 类。观察下下面的改动:

public partial class frmCalculator : Form
{
    PeerThread threadMthods;
    Thread mCalculateFactorsThread;
    Thread mCalculateFactorialThread;
    delegate void UpdateValue(string text);
    public frmCalculator()
    {
        InitializeComponent();

        threadMthods = new PeerThread(); 
    }

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

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

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

  除了定义新的threadMethods 字段为一个新的PeerThread 类,还有两个重要的改动。我们定义了一个新的方法NewFactorsThread(), 它将调用PeerThread 类的无参CalculatorFacotrs()方法。剩下的方法与上一篇的例子一样。

  在cmdFactorial_Click() 方法中,除了在一个线程中执行FactorialThread()方法外,我们也执行了FactorsThread() 方法,让这两个线程无序执行以便于mCalculatorFactors线程有机会等待mCalculatorFactorial 线程。你应该能看到这个是如何被绑定到套接字上的,别着急,我们将会在第七章介绍如何监控一个套接字接口。

  这种模型的常见问题是死锁和阻塞。如果你有一个持续监听某个套接字并从套接字获取数据的线程的话,那么这个套接字可能需要被其他线程锁住。这意味着,对于用来管理类来说这是一个非常好的模型。我们可以使用一个线程监控一个套接字接口然后生成新线程来处理接收到的内容。然而,不要让多个线程监听同一个套接字接口。还有,如果线程在持续地检测套接字接口或者其他资源,那么它会消耗很多额外的处理器周期。正如上面例子显示的那样,你可以使用Thread.Sleep() 来减少非必需的处理器周期。

  在下一篇你将发现这个例子与管道线程模型在概念上非常类似。如果你点击了计算阶乘按钮,你应该有时间继续点击计算因数的按钮,在计算8的阶乘的函数得到40320的结果并显示到文本框中之前它将允许你计算并显示200的因数。