代码改变世界

C# 线程手册 第二章 .NET 中的线程 终止/等待线程

2012-01-16 22:53  DanielWise  阅读(10214)  评论(5编辑  收藏  举报

Abort()方法可以用来终止当前线程。不论何种情况下你想终止线程,比如线程执行了太长时间或者用户取消了之前的决定,Abort()方法都很重要。在一个花费很长时间的搜索进程中你可能想使用这个方法。一个搜索引擎可能在继续运行但是用户已经看到了他们想要的结果,所以用户会终止搜索引擎所运行的线程。当在一个线程外调用Abort()方法时,会引发一个ThreadAbortException异常。如果线程代码中没有捕获这个异常,那么线程将会终止。在为一个可能被多线程上下文访问的方法写异常处理代码时要多考虑一下,比如该使用Catch(ThreadAbortException)的地方不要使用Catch(Exception), 前者属于特定异常且发生后可能不想再恢复,后者属于通用异常。就我们看来,ThreadAbortException 不是很容易停止,你的程序流可能不会按照你所期待的那样继续。

我们来看一个例子。创建一个新工程Destroying,把之前的素数生成代码拷贝到新的Form1.cs中,然后在界面上添加一个Stop按钮。

在Stop按钮点击事件中添加如下代码:

private void cmdStop_Click(object sender, EventArgs e)
{
    //Enable the Start button and disable all others.
    cmdStop.Enabled = false;
    cmdPause.Enabled = false;
    cmdResume.Enabled = false;
    cmdStart.Enabled = true;
    //Destroy the thread.
    primeNumberThread.Abort();
}

这个例子与之前的很像。唯一的不同的是当用户单击Stop按钮时我们使用Abort()方法来终止线程。然后我们启用开始按钮并禁用所有其他按钮。你可能已经注意到ThreadAbortException是一个特别的异常。与所有其他可以捕获的异常一样,一旦异常代码块完成,异常将会被自动重新抛出。当异常被抛出以后,运行时在杀死线程之前运行所有的最终代码块。

 

线程等待(关联)

Join()方法会阻塞一个给定的线程直到当前线程终止。当我们自一个给定的线程实例上调用Join()方法时,线程将会进入WaitSleepJoin状态。如果一个线程依赖另外一个线程的话,那么这个方法非常重要。通过简单地将两个线程关联我们可以说正在运行的线程会进入WaitSleepJoin状态且不会回到运行状态除非调用Join()方法的线程完成了自己的任务。这听起来可能有点让人难与理解,但是让我们通过一个例子来简要说明一下这个问题,thread_joining.cs:

/*************************************
/* Copyright (c) 2012 Daniel Dong
 * 
 * Author:Daniel Dong
 * Blog:  www.cnblogs.com/danielWise
 * Email: guofoo@163.com
 * 
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace SimpleThread
{
    public class JoiningThread
    {
        static Thread SecondThread;
        static Thread FirstThread;

        static void First()
        {
            for (int i = 1; i <= 250; i++)
            {
                Console.Write(i + " ");
            }
        }

        static void Second()
        {
            FirstThread.Join();
            for (int i = 251; i <= 500; i++)
            {
                Console.Write(i + " ");
            }
        }

        public static void Main()
        {
            FirstThread = new Thread(new ThreadStart(First));
            SecondThread = new Thread(new ThreadStart(Second));

            FirstThread.Start();
            SecondThread.Start();

            Console.ReadLine();
        }
    }
}

这个例子的目的是向控制台顺序地输出数字,从1到500. First()方法将会输出前250个数字而Second()方法将会输出后250个。如果不在Second()方法中加FirstThread.Join()的话,执行上下文将会在两个方法之间来回切换,而我们的输出会很乱(试着将这行代码注释掉,然后重新运行一次)。通过Second()方法中调用FirstThread.Join()方法,Second()方法的执行会暂停直到FirstThread执行完。

Join()方法是重载的;它可以接受一个整型数或者一个TimeSpan类型值作为唯一的参数并返回一个布尔型值。调用这两个重载方法的任何一个的效果是线程会阻塞直到另外一个线程完成或者等待时间超时,哪个先发生哪个就起作用。如果线程已经完成那么返回值是true 否则 是false.

 

为什么不对所有方法使用线程?

我们已经看了是用线程的几个非常有用的优势;我们可以让多个线程同时运行,可以让一个进程内运行多个线程。有这么多好处,为什么我们不对所有方法都使用线程?这样的话难道不会让所有代码都执行地更快?不完全是。事实上,我们将在这部分看到过度使用线程而导致相反的结果。

多线程应用程序需要资源。线程需要内存来存储线程本地存储。你可以想象,线程数量被可用内存总量所限制。内存近来已经不那么贵了,所以很多计算机内部都有大容量内存。然而,你不可做出内存可以满足任意数量线程需求的假设。如果你的程序运行在一个未知的硬件环境下,你不能假设你的程序将有足够的内存。另外,你也不能假设你的进程将是唯一的使用系统资源的线程。就算计算机有很多内存,那也不意味着它全是为你的程序准备的。

你将发现线程也会增加额外的处理器负担。在你的程序中创建过多的线程将限制你的线程执行时间。因为你的处理器可能花费更多时间在线程上下文切换上而不是执行你的线程的指令上。如果你的应用程序创建过多线程,你的应用程序将会比所有其他有相对少线程数的应用程序获得更多执行时间。

为了让这个概念更易于理解,我们举个当地百货商店的并发例子。两个收银员在为顾客扫描商品。然而,只有一个装袋工,他要在两个收银员之间来回切换。装袋工在两台收银机之前来回很快的包装商品,因为商品的堆放速度没有装袋工包装商品的速度快。然而,如果又有两个收银员开了新窗口的话,很明显装袋工将会花费更多时间在收银机之间来回跑动而不是包装商品。最终,商店不得不再请一个装袋工。这种情况下把收银员想象成应用程序-或者是线程,把装袋工当做处理器。处理器需要在不同线程间切换。由于线程数量增加,杂货店不得不增加一个处理器来保证用户能够有耐心等待。

“很多线程”是一个通用术语。一个系统上的“很多”到底指代多少并不是确定的。由于硬件配置很大程度上影响一个系统可以运行的线程数量,“很多”就变成一个不确定的值同时也没有特定配置细节和测试信息。

基于这些原因微软建议在应用程序中使用尽量少的线程。这将减少对操作系统资源的要求。

 

下一篇将介绍使用线程的优势…