C#多线程编程实践

来源:互联网 发布:windows活动目录的功能 编辑:程序博客网 时间:2024/04/29 01:20
本文列举几种常用的c#多线程编程实例,其中主要参考了周公 
http://blog.csdn.net/zhoufoxcn/article/details/4402999的文章,特此感谢。
-. WinForm多线程编程
1. new Thread()

    新开一个线程,执行一个方法,没有参数传递,ThreadStart:

private void DoWork() {    Thread t = new Thread(new ThreadStart(this.DoSomething));//使用函数名称作为参数    t.Start();    }    private void DoSomething() {    Console.WriteLine("thread start");    }

新开一个线程,执行一个方法,并传递参数,ParameterizedThreadStart: 

private void DoWork() {    Thread t = new Thread(new ParameterizedThreadStart(this.DoSomething));    t.Start("test");    }private void DoSomething(object o) {     Console.WriteLine(o.ToString()); }

参数定义为object类型。
再一个例子:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading;namespace CShape{    class Program    {        static void Main(string[] args)        {            Program g = new Program();            Thread parath = new Thread(new ParameterizedThreadStart(g.ParameterRun));            parath.Name = "Thread A";            parath.Start(500);//线程启动之后,线程的实例不必再存在.参数传递。            parath = new Thread(new ParameterizedThreadStart(g.ParameterRun));//故重新实例化            parath.Name = "Thread B";            parath.Start(1000);        }        public void ParameterRun(object ms)        {            int j = 10;            int.TryParse(ms.ToString(),out j);            for (int i = 0; i < 10; i++)            {                Console.WriteLine(Thread.CurrentThread.Name+"---当前时间为:" + DateTime.Now);                Thread.Sleep(j);//阻塞当前线程j毫秒            }        }    }}


运行结果如下图:



2. 多线程安全
当多个线程同时访问同一变量时,可能会导致变量的值出错,如典型的生产者和消费者进程。
常用的实现线程的同步有几种方法:lock、Mutex、Monitor、Semaphore、Interlocked和ReaderWriterLock等。


2.1 lock锁同步
lock语句是一种有效的、不跨越多个方法的小代码块同步的做法,也就是使用lock语句只能在某个方法的部分代码之间,不能跨越方法。

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading;namespace ThreadSynchronization{    class MyThread    {        private List<string> list;        private Thread th1;        private Thread th2;        private object objlock = new object();        public MyThread()        {            int N = 20;            list = new List<string>(N);            for (int i = 1; i <= N; i++)                list.Add(i.ToString().PadLeft(2, '0'));            th1 = new Thread(new ThreadStart(Run));            th1.Name = "Thread 1";            th2 = new Thread(new ThreadStart(Run));            th2.Name = "Thread 2";            th1.Start();            th2.Start();        }        //lock锁同步        private void Run()        {            while (list.Count > 0)            {                lock (objlock)//一个线程访问当前区域时,另一线程无法访问,直到访问完                {                    Console.WriteLine(Thread.CurrentThread.Name + "---" + list[0]);                    list.RemoveAt(0);                    Thread.Sleep(1);                }            }        }static void Main(string[] args)        {            MyThread th = new MyThread();            Console.ReadLine();        }    }}

运行结果如下:



2.2 Mointor同步
与使用lock关键字来保持同步的差别不大:”lock (objLock){“被换成了”Monitor.Enter(objLock);”,”}”被换成了” Monitor.Exit(objLock);”。实际上通过其它方式查看最终生成的IL代码,会发现使用lock关键字的代码实际上是用Monitor来实现的。

lock (objLock){//同步代码}
相当于

try{Monitor.Enter(objLock);//同步代码}finally{Monitor.Exit(objLock);}

finally中的代码块确保了即使同步代码出现了异常也仍能释放同步锁。
同上例:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading;namespace ThreadSynchronization{    class MyThread    {        private List<string> list;        private Thread th1;        private Thread th2;        private object objlock = new object();        public MyThread()        {            int N = 20;            list = new List<string>(N);            for (int i = 1; i <= N; i++)                list.Add(i.ToString().PadLeft(2, '0'));            th1 = new Thread(new ThreadStart(Run2));            th1.Name = "Thread 1";            th2 = new Thread(new ThreadStart(Run2));            th2.Name = "Thread 2";            th1.Start();            th2.Start();        }       private void Run2()        {            while (list.Count > 0)            {                Monitor.Enter(objlock);//锁同步                Console.WriteLine(Thread.CurrentThread.Name + "---" + list[0]);                list.RemoveAt(0);                Thread.Sleep(1);                Monitor.Exit(objlock);//释放锁            }        }static void Main(string[] args)        {            MyThread th = new MyThread();            Console.ReadLine();        }    }}

运行结果与上例一样:



2.3 Wait()和Pulse()方法同步
Monitor类出了Enter()和Exit()方法之外,还有Wait()和Pulse()方法。Wait()方法是临时释放当前获得的锁,并使当前对象处于阻塞状态,Pulse()方法是通知处于等待状态的对象准备就绪,释放等待锁。但一定要保证先调用Wait()方法的进程先运行,因为如果Pulse()方法的进程先运行,此时没有等待锁,不释放锁,会导致两个线程均运行到Wait()方法,阻塞各自的进程,从而导致死锁。可以让先执行Pulse()方法的进程sleep一定时间。
举例:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading;namespace ThreadSynchronization{    class MyThread2    {        private int num;        private Random ran;        private object objlock;        private Thread th1;        private Thread th2;        private int N;        public MyThread2()        {            ran = new Random();            objlock = new object();            th1 = new Thread(new ThreadStart(ShowNum));            th2 = new Thread(new ThreadStart(GenNum));            N = 10;            th1.Start();//先调用Wait()方法的进程先启动,避免死锁            th2.Start();        }        private void GenNum()        {            Monitor.Enter(objlock);            Console.WriteLine("进入当前进程"+Thread.CurrentThread.GetHashCode());            for (int i = 0; i < N; i++)            {                Monitor.Pulse(objlock);//通知其它等待锁当前对象状态已经发生改变,当这个对象释放锁之后等待锁的对象将会获得锁                 Console.WriteLine("WaitAndPluse2:工作");                  num = ran.Next(DateTime.Now.Millisecond);                Console.WriteLine("WaitAndPluse2:生成了数据,number=" + num +                     ",Thread ID=" + Thread.CurrentThread.GetHashCode());                Monitor.Wait(objlock);//释放对象锁,并阻止当前线程              }            Console.WriteLine("退出当前线程:" + Thread.CurrentThread.GetHashCode());              Monitor.Exit(objlock);        }        private void ShowNum()        {            Monitor.Enter(objlock);            Console.WriteLine("进入当前进程" + Thread.CurrentThread.GetHashCode());            for (int i = 0; i < N; i++)            {                Monitor.Wait(objlock);//释放对象锁,并阻止当前线程                  Console.WriteLine("WaitAndPluse1:工作");                Console.WriteLine("WaitAndPluse1:显示数据,number=" + num +                    ",Thread ID=" + Thread.CurrentThread.GetHashCode());                Monitor.Pulse(objlock);//继续执行当前线程,并通知其他等待锁去执行            }            Console.WriteLine("退出当前线程:" + Thread.CurrentThread.GetHashCode());              Monitor.Exit(objlock);        }static void Main(string[] args)        {            MyThread2 th = new MyThread2();            Console.ReadLine();        }    }}

运行结果:



3. 线程池ThreadPool
如果创建了过多的线程将会增加操作系统资源的占用,并且还要处理资源要求和潜在的占用冲突
,并且使用了多线程之后将使代码的执行流程和资源竞争情况变得复杂,新开一个线程代价是很高昂的,如果我们每个操作都新开一个线程,那么太浪费了,所以我们用线程池ThreadPool来管理多线程。
使用线程池有如下优点:
1、缩短应用程序的响应时间。因为在线程池中有线程的线程处于等待分配任务状态(只要没有
超过线程池的最大上限),无需创建线程。
2、不必管理和维护生存周期短暂的线程,不用在创建时为其分配资源,在其执行完任务之后释放资源。
3、线程池会根据当前系统特点对池内的线程进行优化处理。

ThreadPool是一个静态类,它没有构造函数,对外提供的函数也全部是静态的。其中有一个QueueUserWorkItem方法,它有两种重载形式,如下:public static bool QueueUserWorkItem(WaitCallback callBack):将方法排入队列以便执行。
此方法在有线程池线程变得可用时执行。
public static bool QueueUserWorkItem(WaitCallback callBack,Object state):将方法排入队列以便执行,并指定包含该方法所用数据的对象。此方法在有线程池线程变得可用时执行。
QueueUserWorkItem方法中使用的的WaitCallback参数表示一个delegate,它的声明如下:
public delegate void WaitCallback(Object state)
如果需要传递任务信息可以利用WaitCallback中的state参数,类似于
ParameterizedThreadStart委托。
举例:
直接在例2.3上面修改即可

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading;namespace ThreadSynchronization{    class MyThread3    {        private int num;        private Random ran;        private object objlock;        private int N;        public MyThread3()        {            ran = new Random();            objlock = new object();            N = 10;            //一定要保证先执行Monitor.Wait的进程先运行到Monitor.Wait的地方,才能保证不出现死锁            //运行到Monitor.Wait时,等待另一线程调用Monitor.Pulse激活;否则如果先执行Monitor.Pulse的            //线程先执行的话,导致两个线程的Monitor.Wait都不能激活,从而导致死锁            ThreadPool.QueueUserWorkItem(new WaitCallback(ShowNum));            ThreadPool.QueueUserWorkItem(new WaitCallback(GenNum));//线程池顺序颠倒后造成死锁        }        private void GenNum(object ob)        {            Monitor.Enter(objlock);            //Thread.Sleep(1000);            Console.WriteLine("进入当前进程"+Thread.CurrentThread.GetHashCode());            for (int i = 0; i < N; i++)            {                                Monitor.Pulse(objlock);//通知其它等待锁当前对象状态已经发生改变,当这个对象释放锁之后等待锁的对象将会获得锁                 Console.WriteLine("WaitAndPluse2:工作");                  num = ran.Next(DateTime.Now.Millisecond);                Console.WriteLine("WaitAndPluse2:生成了数据,number=" + num +                     ",Thread ID=" + Thread.CurrentThread.GetHashCode());                                Monitor.Wait(objlock);//释放对象锁,并阻止当前线程              }            Console.WriteLine("退出当前线程:" + Thread.CurrentThread.GetHashCode());              Monitor.Exit(objlock);        }        private void ShowNum(object ob)        {            Monitor.Enter(objlock);            Console.WriteLine("进入当前进程" + Thread.CurrentThread.GetHashCode());            for (int i = 0; i < N; i++)            {                Monitor.Wait(objlock);//释放对象锁,并阻止当前线程                  Console.WriteLine("WaitAndPluse1:工作");                Console.WriteLine("WaitAndPluse1:显示数据,number=" + num +                    ",Thread ID=" + Thread.CurrentThread.GetHashCode());                Monitor.Pulse(objlock);//继续执行当前线程,并通知其他等待锁去执行            }            Console.WriteLine("退出当前线程:" + Thread.CurrentThread.GetHashCode());              Monitor.Exit(objlock);        }    static void Main(string[] args)        {            MyThread3 th = new MyThread3();            Console.ReadLine();        }    }}

结果:



二. Invoke
1. this.Invoke
现在,在业务线程里面执行完毕,要改变窗体控件的值了,此时,如果直接通过this得到控件的
句柄,然后对它进行操作是会抛异常的,.Net WinForm Application里面是不允许这样的操作的。这时,可以调用Invoke方法。
2.Invoke方法签名:
object Control.Invoke(Delegate Method)
object Control.Invoke(Delegate Method, params object[] args)
3.Invoke方法举例

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;namespace ThreadPoolDemo{    public partial class ThreadForm : Form    {        //定义delegate以便Invoke时使用        private delegate void SetProgressBarValue(int value);        public ThreadForm()        {            InitializeComponent();        }        private void btnThread_Click(object sender, EventArgs e)        {            progressBar.Value = 0;            Thread thread = new Thread(new ThreadStart(Run));//方法一,使用线程            thread.Start();        }        //使用线程来直接设置进度条        private void Run()        {            while (progressBar.Value < progressBar.Maximum)            {                progressBar.PerformStep();            }        }        private void btnInvoke_Click(object sender, EventArgs e)        {            progressBar.Value = 0;            Thread thread = new Thread(new ThreadStart(RunWithInvoke));            thread.Start();        }        //使用Invoke方法来设置进度条        private void RunWithInvoke()        {            int value = progressBar.Value;            while (value< progressBar.Maximum)            {                //如果是跨线程调用                if (InvokeRequired)                {                    this.Invoke(new SetProgressBarValue(SetProgressValue), value++);//用委托初始化Invoke参数                }                else                {                    progressBar.Value = ++value;                }            }        }        //跟SetProgressBarValue委托相匹配的方法        private void SetProgressValue(int value)        {            progressBar.Value = value;        }    }}

4.BackgroundWorker类操作
该类的事件包括DoWork、ProgressChanged、RunWorkerCompleted,使用时均要指定函数执行。方法ReportProgress(Int32)引发ProgressChanged事件。

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;namespace ThreadPoolDemo{    public partial class ThreadForm : Form    {        //定义delegate以便Invoke时使用        private delegate void SetProgressBarValue(int value);        private BackgroundWorker worker;        public ThreadForm()        {            InitializeComponent();        }        private void btnBackgroundWorker_Click(object sender, EventArgs e)        {            progressBar.Value = 0;            worker = new BackgroundWorker();            worker.DoWork += new DoWorkEventHandler(worker_DoWork);            //当工作进度发生变化时执行的事件处理方法            worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);            //当事件处理完毕后执行的方法            worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);            worker.WorkerReportsProgress = true;//支持报告进度更新            worker.WorkerSupportsCancellation = false;//不支持异步取消            worker.RunWorkerAsync();//启动执行            btnBackgroundWorker.Enabled = false;        }        //当事件处理完毕后执行的方法        void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)        {            btnBackgroundWorker.Enabled=true;        }        //当工作进度发生变化时执行的事件处理方法        void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)        {            //可以在这个方法中与界面进行通讯            progressBar.Value = e.ProgressPercentage;        }        //开始启动工作时执行的事件处理方法        void worker_DoWork(object sender, DoWorkEventArgs e)        {            int value = progressBar.Value;            while (value < progressBar.Maximum)            {                worker.ReportProgress(++value);//引发ProgressChanged 事件            }        }    }}


0 0
原创粉丝点击