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

摘要:
如果线程代码中未捕捉到异常,则线程将终止。抛出异常后,运行时在终止线程之前运行所有最终代码块。线程等待(关联)Join()方法阻塞给定线程,直到当前线程终止。事实上,我们将在本节中看到,过度使用线程会导致相反的结果。在程序中创建太多线程将限制线程执行时间。出于这些原因,Microsoft建议在应用程序中使用尽可能少的线程。

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.

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

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

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

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

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

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

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

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

免责声明:文章转载自《C# 线程手册 第二章 .NET 中的线程 终止/等待线程》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Android源码分析(二)-----如何编译修改后的framework资源文件Easyui中 $.messager.alert出现的位置下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

见到的一篇IOCP流程 自己用demo实现了一下, 简单照抄,改动了一点点

要分析的实例分为两个线程: 分别是主线程(MAIN),还有一个是创建的线程(ServerThread) 1.主函数完成初始化工作:   1.1: (主线程)HANDLE hCompletion = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);    创建完成端口对象   1.2: (主线程...

使用 VisualVM 进行性能分析及调优

VisualVM 是一款免费的集成了多个 JDK 命令行工具的可视化工具,它能为您提供强大的分析能力,对 Java 应用程序做性能分析和调优。这些功能包括生成和分析海量数据、跟踪内存泄漏、监控垃圾回收器、执行内存和 CPU 分析,同时它还支持在 MBeans 上进行浏览和操作。本文主要介绍如何使用 VisualVM 进行性能分析及调优。   概述 开发大...

CyclicBarrier:人齐了,老司机就可以发车了!

上一篇咱讲了 CountDownLatch 可以解决多个线程同步的问题,相比于 join 来说它的应用范围更广,不仅可以应用在线程上,还可以应用在线程池上。然而 CountDownLatch 却是一次性的计数器,以王者农药来说,咱们不可能一场团战就决定比赛的输赢,所以在某些场景下,咱们是需要重复使用某个等待功能的,这就是我们今天要介绍的另一个主角——Cyc...

java 基本理论知识点

http://www.cnblogs.com/hellokitty1/p/4491808.html 1、main方法是怎么写的        public static void main(String [] args){}//最习惯的      public static void main(String  args[]){}      static p...

使用VS调试时,被调试进程如何被断下来的。

不是什么新鲜的东西,很多书上写的非常详细了,不过有人问到,简要的扯下这个VS如何断下进程的。 ++++++++++++++++++++++++++ 当用VS的时候,按下F10后,为什么能够停在程序的入口处呢? 这个问题首先从windows API CreateProcess的Process Creation Flags说起。如果在创建进程的时候加了这个D...

实现一个双缓冲队列

在生产者-消费者模式中,我们常常会使用到队列,这个队列在多个线程共享访问时存在互斥和竞争操作, 意味着每次访问都要加锁。如何更好的如何减少锁竞争次数呢 ?今天要介绍的双缓冲队列就是个不错的选择。 双缓冲队列就是冲着同步/互斥的开销来的。我们知道,在多个线程并发访问同一个资源的时候,需要特别注意线程的同步问题。稍稍不注意,噢货,程序结果不正确了。 原理 直...