黑马程序员-.NET基础之多线程

------- Windows Phone 7手机开发.Net培训、期待与您交流! -------

   

    在黑马论坛上提出了一个关于多线程的定时任务的问题,可能是我们Asp.Net板块论坛太冷清了,回答的人很少,也有可能自己提出的问题too easy,别人不屑回答。现将自己关于多线程学到整理成笔记,供大家参考。

一、线程处理概述

进程是操作系统中正在执行的不同应用程序的一个实例,操作系统把不同的进程分离开来。
线程是操作系统分配处理器时间的基本单元,每个线程都维护异常处理程序、调度优先级和一组系统用于在调度该线程前保存线程上下文的结构。
每个应用程序域都是用单个线程启动的(应用程序的入口点Main方法),应用程序域中的代码可以创建附加应用程序域和附加线程。

线程的优缺点

线程处理使程序能够执行并发处理,因而特别适合需要同时执行多个操作的场合。
多线程处理可解决用户响应性能和多任务的问题,但同时引入了资源共享和同步问题等问题。

二、创建多线程应用程序

    应用程序运行时,将创建新的应用程序域。当运行环境调用应用程序的入口点(Main方法)时,将创建应用程序主线程。下面给出一段主线程示例。

using System;
using System.Threading;
namespace CSharpPractice.Threading
{
    public class WorkerThreadExample
    {
        static void Main()
        {
            Console.WriteLine("主线程:开始……");
            // 主线程睡眠5秒钟
            Thread.Sleep(5000);
            Console.WriteLine("主线程:结束。");
            Console.ReadLine();
        }
    }
}


 

2.创建和启动新线程

主线程以外的线程一般称之为工作线程。创建新线程的大致步骤如下:
创建一个将在主线程外执行的函数,即类的方法,用于执行新线程要执行的逻辑操作
在主线程(Main方法)中创建一个Thread的实例,指向步骤1中的函数。例如:Thread newThread = new Thread(anObject.AMethod)
调用创建的Thread的实例的Start()方法,以启动新线程。例如:newThread.Start()。创建和启动新线程示例。

using System;
using System.Threading;
namespace CSharpPractice.Threading
{
    public class Worker
    {    // 工作线程执行逻辑的实现方法.
        public void DoWork()
        {
            while (!_shouldStop)
            {
                Console.WriteLine("工作线程:working...");
            }
            Console.WriteLine("工作线程:terminating gracefully.");
        }
        public void RequestStop()
        {
            _shouldStop = true;
        }
        // 使用Volatile以启发编译器,本数据成员将被多线程访问.
        private volatile bool _shouldStop;
    }
    public class WorkerThreadExample
    {
        static void Main()
        {
            Console.WriteLine("主线程:Starting worker thread...");
            // 创建工作线程对象。但不启动线程.
            Worker workerObject = new Worker();
            Thread workerThread = new Thread(workerObject.DoWork);
            // 启动工作线程.
            workerThread.Start();
            // 循环直至激活工作线程.
            while (!workerThread.IsAlive) ;
            // 让主线程睡眠1毫秒,以允许工作线程完成自己的工作:
            Thread.Sleep(1);
            // 要求工作线程停止自己:
            workerObject.RequestStop();
            // 使用Join方法阻止当前线程,直至对象线程终止.
            workerThread.Join();
            Console.WriteLine("主线程:Worker thread has terminated.");
            Console.ReadLine();
        }
    }
}


 

3.暂停、中断和销毁线程

调用Thread.Sleep方法会导致当前线程立即阻止,阻止时间的长度等于传递给Thread.Sleep 的毫秒数。注意:一个线程不能针对另一个线程调用Thread.Sleep。
暂停线程示例

using System;
using System.Threading;
namespace CSharpPractice.Threading
{
    class PauseThreadTest
    {
        static void Main()
        {
            Console.WriteLine("主线程启动...");
            //创建并启动工作线程
            Thread newThread = new Thread(ThreadMethod);
            //newThread.SetApartmentState(ApartmentState.MTA);
            newThread.Start();
            Console.WriteLine("主线程睡眠300ms...");
            Thread.Sleep(300);
            Console.WriteLine("主线程终止...");
            Console.ReadKey();
        }
        static void ThreadMethod()
        {
            Console.WriteLine("工作线程启动...");
            Console.WriteLine("工作线程睡眠1000ms...");
            Thread.Sleep(1000);
            Console.WriteLine("工作线程终止...");
        }
    }
}

 

通过对被阻止的线程调用Thread.Interrupt,可以中断正在等待的线程并引发ThreadInterruptedException,从而使该线程脱离造成阻止的调用。
中断线程示例

using System;
using System.Security.Permissions;
using System.Threading;
namespace CSharpPractice.Threading
{
    class ThreadInterrupt
    {
        static void Main()
        {
            StayAwake stayAwake = new StayAwake();
            Thread newThread = new Thread(stayAwake.ThreadMethod);
            newThread.Start();
            // 如果newThread 当前正被阻止或者将被阻止
            // 下面的代码将在ThreadMethod中产生一个异常
            Console.WriteLine("主线程调用newThread的Interrupt方法.");
            newThread.Interrupt();
            // 等待newThread结束.
            newThread.Join();
            Console.ReadKey();
        }
    }

    class StayAwake
    {
        public void ThreadMethod()
        {
            Console.WriteLine("newThread正在执行ThreadMethod……");
            try
            {
                Console.WriteLine("newThread 将进入睡眠……");
                Thread.Sleep(Timeout.Infinite);
            }
            catch (ThreadInterruptedException e)
            {
                Console.WriteLine("newThread 被主线程中断.");
            }
        }
    }
}


Abort 方法用于永久地停止也即销毁托管线程。调用Abort时,公共语言运行库在目标线程中引发ThreadAbortException,目标线程可捕捉此异常。
销毁线程示例

using System;
using System.Threading;
namespace CSharpPractice.Threading
{
    class Test
    {
        public static void Main()
        {
            Thread newThread = new Thread(new ThreadStart(TestMethod));
            newThread.Start();
            Thread.Sleep(1000);
            // 销毁newThread.
            Console.WriteLine("主程序销毁新线程……");
            newThread.Abort("来自于主程序的信息.");
            // 等待线程终止.
            newThread.Join();
            Console.WriteLine("新线程终止 – 退出主程序.");
            Console.ReadLine();
        }
        static void TestMethod()
        {
            try
            {
                while (true)
                {
                    Console.WriteLine("新线程运行中……");
                    Thread.Sleep(1000);
                }
            }
            catch (ThreadAbortException abortException)
            {
                Console.WriteLine((string)abortException.ExceptionState);
            }
        }
    }
}


 

三、线程优先级和线程调度

每个线程都有一个分配的优先级,在运行库内创建的线程最初被分配 Normal 优先级。通过线程的Priority属性可以获取和设置其优先级。下面是线程优先级和线程调度示例。

using System;
using System.Threading;
namespace CSharpPractice.Threading
{
    class PriorityTest
    {
        bool loopSwitch;
        public PriorityTest()
        {
            loopSwitch = true;
        }
        public bool LoopSwitch
        {
            set { loopSwitch = value; }
        }
        public void ThreadMethod()
        {
            long threadCount = 0;
            while (loopSwitch)
            {
                threadCount++;
            }
            Console.WriteLine("{0} with {1,11} priority " +
                "has a count = {2,13}", Thread.CurrentThread.Name,
                Thread.CurrentThread.Priority.ToString(),
                threadCount.ToString("N0"));
        }
    }
    class Test
    {
        static void Main()
        {
            PriorityTest priorityTest = new PriorityTest();
            ThreadStart startDelegate =
                new ThreadStart(priorityTest.ThreadMethod);
            Thread threadOne = new Thread(startDelegate);
            threadOne.Name = "ThreadOne";
            Thread threadTwo = new Thread(startDelegate);
            threadTwo.Name = "ThreadTwo";
            threadTwo.Priority = ThreadPriority.BelowNormal;
            Console.WriteLine("启动线程1(优先级默认为Normal)......");
            threadOne.Start();
            Console.WriteLine("启动线程2(优先级设置为BelowNormal)......");
            threadTwo.Start();
            // 允许数10 seconds.
            Console.WriteLine("请耐心等待5秒......");
            Thread.Sleep(5000);
            priorityTest.LoopSwitch = false;
            Console.ReadLine();
        }
    }
}


 

四、线程同步

    当多个线程可以调用单个对象的属性和方法时,一个线程可能会中断另一个线程正在执行的任务,使该对象处于一种无效状态。因此必须针对这些调用进行同步处理。可以使用lock语句同步代码块,或使用监视器同步代码块。

 

2.使用lock语句同步代码块

lock语句使用lock关键字将语句块标记为临界区,方法是获取给定对象的互斥锁,执行语句,然后释放该锁。lock关键字可以确保当一个线程位于代码的临界区时,另一个线程不会进入该临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。代码块完成运行,而不会被其他线程中断。
lock语句以关键字lock开头,并以一个对象作为参数,在该参数的后面为线程互斥的代码块。
使用lock语句同步代码块示例

using System;
using System.Threading;
namespace CSharpPractice.Threading
{
    class Account                   //账户类
    {
        private Object thisLock = new Object();
        int balance;
        Random r = new Random();   //准备生成随机数
        public Account(int initial)     //账户构造函数
        {
            balance = initial;
        }
        int Withdraw(int amount)     //从账户中取款
        {
            // 本条件永远不会为True,除非注释掉lock语句
            if (balance < 0)           //账户余额不足,<=0
            {
                throw new Exception("账户余额不足(<=0)!");
            }
            // 注释掉下面的lock语句,测试lock关键字的效果 
            // lock (thisLock)
            {
                if (balance >= amount)   //账户余额>取款额
                {
                    Console.WriteLine("取款前账户余额: " + balance);  //取款前账户余额
                    Console.WriteLine("取款额(-)     : -" + amount);   //取款额
                    balance = balance - amount;
                    Console.WriteLine("取款后账户余额: " + balance);  //取款后账户余额
                    return amount;
                }
                else
                {
                    return 0;   // 拒绝交易
                }
            }
        }
        public void DoTransactions()  // 执行交易DoTransactions()
        {  // 从账户中取100次钱款,每次取款额为1~100中的随机数
            for (int i = 0; i < 100; i++)
            {
                Withdraw(r.Next(1, 100));
            }
        }
    }
    class Test
    {
        static void Main()
        {
            Thread[] threads = new Thread[10];    //线程数组(10个元素)
            Account acc = new Account(1000);      //新建账户对象,初始存款额为1000
            for (int i = 0; i < 10; i++)          //循环10次
            {   //每次开始新的线程,执行账户交易DoTransactions()
                Thread t = new Thread(new ThreadStart(acc.DoTransactions));
                threads[i] = t;
            }
            for (int i = 0; i < 10; i++)          //循环10次
            {
                threads[i].Start();               //开始线程
            }
            Console.ReadLine();
        }
    }
}

 

3.使用监视器同步代码块

    使用监视器(Monitor)也可以防止多个线程同时执行代码块。调用Monitor.Enter方法,允许一个且仅一个线程继续执行后面的语句;其他所有线程都将被阻止,直到执行语句的线程调用Exit。

 

4.同步事件和等待句柄

同步事件允许线程通过发信号互相通信,从而实现线程需要独占访问的资源的同步处理控制
同步事件有两种:AutoResetEvent(自动重置的本地事件)和 ManualResetEvent(手动重置的本地事件)
每种事件又包括两种状态:收到信号状态(signaled)和未收到信号状态(unsignaled)。下面是同步事件和等待句柄示例。

using System;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
namespace CSharpPractice.Threading
{
    public class SyncEvents
    {
        public SyncEvents()
        {
            _newItemEvent = new AutoResetEvent(false);
            _exitThreadEvent = new ManualResetEvent(false);
            _eventArray = new WaitHandle[2];
            _eventArray[0] = _newItemEvent;
            _eventArray[1] = _exitThreadEvent;
        }
        public EventWaitHandle ExitThreadEvent
        {
            get { return _exitThreadEvent; }
        }
        public EventWaitHandle NewItemEvent
        {
            get { return _newItemEvent; }
        }
        public WaitHandle[] EventArray
        {
            get { return _eventArray; }
        }
        private EventWaitHandle _newItemEvent;
        private EventWaitHandle _exitThreadEvent;
        private WaitHandle[] _eventArray;
    }
    public class Producer             //制造者线程
    {
        public Producer(Queue<int> q, SyncEvents e)  //构造函数
        {
            _queue = q;
            _syncEvents = e;
        }
        // Producer.ThreadRun方法一直循环,直到“退出线程”事件变为终止状态。
        // 此事件的状态由 WaitOne 方法使用 SyncEvents 类定义的 ExitThreadEvent 属性进行测试。
        // 在这种情况下,检查该事件的状态不会阻止当前线程,
        // 因为 WaitOne 使用的第一个参数为零,这表示该方法应立即返回。
        // 如果 WaitOne 返回 true,则说明该事件当前处于终止状态。
        // ThreadRun 方法将返回,其效果相当于终止执行此方法的辅助线程。
        public void ThreadRun()
        {
            int count = 0;
            Random r = new Random();
            // 在“退出线程”事件终止前,Producer.ThreadStart 方法将尝试在队列中保留 20 项。
            // 项只是 0 到 100 之间的一个整数。在添加新项前,必须锁定该集合,
            // 以防止使用者线程和主线程同时访问该集合。这一点是使用 lock 关键字完成的。
            // 传递给 lock 的参数是通过 ICollection 接口公开的 SyncRoot 字段。
            // 此字段专门为同步线程访问而提供。对该集合的独占访问权限
            // 被授予 lock 后面的代码块中包含的所有指令。对于制造者添加
            // 到队列中的每个新项,都将调用“新项”事件的 Set 方法。
            // 这将通知使用者线程离开挂起状态并开始处理新项。
            while (!_syncEvents.ExitThreadEvent.WaitOne(0, false))
            {
                lock (((ICollection)_queue).SyncRoot)
                {
                    while (_queue.Count < 20)
                    {
                        _queue.Enqueue(r.Next(0, 100));
                        _syncEvents.NewItemEvent.Set();
                        count++;
                    }
                }
            }
            Console.WriteLine("制造者线程: produced {0} items", count);
        }
        private Queue<int> _queue;
        private SyncEvents _syncEvents;
    }
    public class Consumer       //使用者线程
    {
        public Consumer(Queue<int> q, SyncEvents e)
        {
            _queue = q;
            _syncEvents = e;
        }
        // Consumer.ThreadRun
        // Consumer 对象还定义名为 ThreadRun 的方法。
        // 与制造者的 ThreadRun 类似,此方法由 Main 方法创建的辅助线程执行。
        // 然而,使用者的 ThreadStart 必须响应两个事件。
        public void ThreadRun()
        {
            int count = 0;
            // 使用 WaitAny 阻止使用者线程,直到所提供的数组中的任意一个
            // 等待句柄变为终止状态。在这种情况下,数组中有两个句柄,
            // 一个用来终止辅助线程,另一个用来指示有新项添加到集合中。
            // WaitAny 返回变为终止状态的事件的索引。“新项”事件是数组中的
            // 第一个事件,因此索引零表示新项。在这种情况下,检查索引 1
            //(它指示“退出线程”事件),并使用它来确定此方法是否继续使用项。
            // 如果“新项”事件处于终止状态,将通过 lock 获得对集合的
            // 独占访问权限并使用新项。因为此示例生成并使用数千个项,
            // 所以不显示使用的每个项,而是使用 Main 定期显示队列中的内容
            while (WaitHandle.WaitAny(_syncEvents.EventArray) != 1)
            {
                lock (((ICollection)_queue).SyncRoot)
                {
                    int item = _queue.Dequeue();
                }
                count++;
            }
            Console.WriteLine("使用者线程: consumed {0} items", count);
        }
        private Queue<int> _queue;
        private SyncEvents _syncEvents;
    }
    public class ThreadSyncSample
    {   // 线程同步示例:使用 lock 获得对队列的独占访问权限
        private static void ShowQueueContents(Queue<int> q) //此方法由主线程执行的(被Main调用)
        {
            lock (((ICollection)q).SyncRoot)
            {   //  对整个集合进行枚举
                foreach (int item in q)
                {
                    Console.Write("{0} ", item);
                }
            }
            Console.WriteLine();
        }
        static void Main()
        {   // 创建一个队列(该队列的内容将被生成和使用)
            Queue<int> queue = new Queue<int>();
            // 创建SyncEvents 的一个实例
            SyncEvents syncEvents = new SyncEvents();
            Console.WriteLine("配置制造者线程和使用者线程......");
            // 配置 Producer 和 Consumer 对象以供辅助线程使用
            Producer producer = new Producer(queue, syncEvents);
            Consumer consumer = new Consumer(queue, syncEvents);
            Thread producerThread = new Thread(producer.ThreadRun);
            Thread consumerThread = new Thread(consumer.ThreadRun);
            // 调用 Start 方法来启动两个辅助线程。
            // 独立于当前正在执行 Main 方法的主线程开始异步执行过程
            Console.WriteLine("启动制造者线程和使用者线程......");
            producerThread.Start();
            consumerThread.Start();
            // Main通过调用 Sleep 方法将主线程挂起。
            for (int i = 0; i < 4; i++)            //重复四次
            {  // 将当前正在执行的线程挂起2500毫秒
                Thread.Sleep(2500);
                // 2500毫秒后,Main 将重新激活,显示队列的内容
                ShowQueueContents(queue);
            }
            // Main通过调用“退出线程”事件的 Set 方法通知辅助线程终止
            Console.WriteLine("发信号,告知终止线程......");
            syncEvents.ExitThreadEvent.Set();
            //对每个辅助线程调用 Join 方法以阻止主线程,
            //直到每个辅助线程都响应该事件并终止。
            producerThread.Join();
            consumerThread.Join();
            Console.ReadLine();
        }
    }

 

5.使用Mutex同步代码块

mutex(mutually exclusive,互斥体)由 Mutex 类表示,与监视器(Monitor)类似,用于防止多个线程在某一时间同时执行某个代码块。
与监视器不同的是,mutex 可以用来使跨进程的线程同步。尽管mutex可以用于进程内的线程同步,但是它会消耗更多的计算资源,所以进程内的线程同步建议使用监视器(Monitor)。
当用于进程间同步时,mutex 称为“命名mutex”,因为它将用于另一个应用程序,因此它不能通过全局变量或静态变量共享。必须给它指定一个名称,才能使两个应用程序访问同一个mutex 对象。
Mutex 有两种类型:未命名的局部 mutex 和已命名的系统 mutex。下面是使用Mutex同步代码块示例。

// 本例显示如何使用“命名mutex”在进程或线程间发送信号。
// 在2个或多个命令行窗口运行本程序。
// 每个进程将创建一个Mutex对象:命名互斥体"MyMutex".
// 命名mutex是一个系统对象,其生命周期
// 由其所代表的Mutex 对象的生命周期所确定。
// 当第一个进程创建其局部Mutex时创建命名mutex。
// 本例中,命名mutex属于第一个进程。
// 当销毁所有Mutex对象时,释放此命名mutex
using System;
using System.Threading;
namespace CSharpPractice.Threading
{
    public class Test
    {
        public static void Main()
        {
            // 创建命名mutex。只能存在一个名为"MyMutex"的系统对象。
            Mutex m = new Mutex(false, "MyMutex");
            // 试图获取对命名mutex的控制权。 
            // 如果命名mutex被另一个线程所控制,则等待直至其被释放 
            Console.WriteLine("等待Mutex. . . . . .");
            m.WaitOne();
            // 保持对mutex的控制,直至用户按ENTER键.
            Console.WriteLine("本应用拥有mutex。请按ENTER键释放之并退出!");
            Console.ReadLine();
            m.ReleaseMutex();
        }
    }
}

 

五、线程池

线程池是可以用来在后台执行多个任务的线程集合,这使主线程可以自由地异步执行其他任务。线程池通常用于服务器应用程序。每个传入请求都将分配给线程池中的一个线程,因此可以异步处理请求,而不会占用主线程,也不会延迟后续请求的处理。
一旦线程池中的某个线程完成任务,它将返回到等待线程队列中,等待被再次使用。这种重用使应用程序可以避免为每个任务创建新线程的开销。

using System;
using System.Threading;
namespace CSharpPractice.Threading
{
    public class Fibonacci
    {
        public Fibonacci(int n, ManualResetEvent doneEvent)
        {
            _n = n;
            _doneEvent = doneEvent;
        }

        // 线程池回调:计算 Fibonacci 结果.
        public void ThreadPoolCallback(Object threadContext)
        {
            int threadIndex = (int)threadContext;
            Console.WriteLine("线程 {0} 开始......", threadIndex);
            _fibOfN = Calculate(_n);
            Console.WriteLine("线程 {0} 结果计算......", threadIndex);
            _doneEvent.Set();
        }
        // 递归方法计算第N个Fibonacci数:F (n ) = F (n - 1) + F(n - 2).
        public int Calculate(int n)
        {
            if (n <= 1)           // F(0)=0; F(1)=1
            {
                return n;
            }
            return Calculate(n - 1) + Calculate(n - 2);
        }
        public int N { get { return _n; } }
        private int _n;
        public int FibOfN { get { return _fibOfN; } }
        private int _fibOfN;
        private ManualResetEvent _doneEvent;
    }
    public class ThreadPoolExample    // 线程池
    {
        static void Main()
        {
            const int FibonacciCalculations = 5;    // 5个Fibonacci数
            // 每个事件用于每个Fibonacci对象
            ManualResetEvent[] doneEvents = new ManualResetEvent[FibonacciCalculations];
            Fibonacci[] fibArray = new Fibonacci[FibonacciCalculations];
            Random r = new Random();
            // 使用ThreadPool配置和启动线程:
            Console.WriteLine("启动 {0} 个任务......", FibonacciCalculations);
            for (int i = 0; i < FibonacciCalculations; i++)
            {
                doneEvents[i] = new ManualResetEvent(false);
                Fibonacci f = new Fibonacci(r.Next(1, 10), doneEvents[i]);
                fibArray[i] = f;
                ThreadPool.QueueUserWorkItem(f.ThreadPoolCallback, i);
            }
            // 等待线程池中的所有线程的计算...
            WaitHandle.WaitAll(doneEvents);
            Console.WriteLine("完成所有计算!");
            // 显示结果:第1项到第10项之间的5个Fibonacci数...
            Console.WriteLine("第1项[F(0)]到第10项[F(9)]之间的任意5个Fibonacci数为----");
            for (int i = 0; i < FibonacciCalculations; i++)
            {
                Fibonacci f = fibArray[i];
                Console.WriteLine("Fibonacci({0}) = {1}", f.N, f.FibOfN);
            }
            Console.ReadLine();
        }
    }
}


 

 

------- Windows Phone 7手机开发.Net培训、期待与您交流! -------

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值