c#多线程的应用全面解析

时间:2021-05-20

1.使用多线程的几种方式

(1)不需要传递参数,也不需要返回参数

ThreadStart是一个委托,这个委托的定义为void ThreadStart(),没有参数与返回值。
复制代码 代码如下:
class Program

{

static void Main(string[] args)

{

for (int i = 0; i < 30; i++)

{

ThreadStart threadStart = new ThreadStart(Calculate);

Thread thread = new Thread(threadStart);

thread.Start();

}

Thread.Sleep(2000);

Console.Read();

}

public static void Calculate()

{

DateTime time = DateTime.Now;//得到当前时间

Random ra = new Random();//随机数对象

Thread.Sleep(ra.Next(10,100));//随机休眠一段时间

Console.WriteLine(time.Minute + ":" + time.Millisecond);

}

}

(2)需要传递单个参数
复制代码 代码如下:
ParameterThreadStart委托定义为void ParameterizedThreadStart(object state),有一个参数但是没有返回值。

class Program

{

static void Main(string[] args)

{

for (int i = 0; i < 30; i++)

{

ParameterizedThreadStart tStart = new ParameterizedThreadStart(Calculate);

Thread thread = new Thread(tStart);

thread.Start(i*10+10);//传递参数

}

Thread.Sleep(2000);

Console.Read();

}

public static void Calculate(object arg)

{

Random ra = new Random();//随机数对象

Thread.Sleep(ra.Next(10, 100));//随机休眠一段时间

Console.WriteLine(arg);

}

}

(3)使用专门的线程类(常用)

使用线程类可以有多个参数与多个返回值,十分灵活!
复制代码 代码如下:
class Program

{

static void Main(string[] args)

{

MyThread mt = new MyThread(100);

ThreadStart threadStart = new ThreadStart(mt.Calculate);

Thread thread = new Thread(threadStart);

thread.Start();

//等待线程结束

while (thread.ThreadState != ThreadState.Stopped)

{

Thread.Sleep(10);

}

Console.WriteLine(mt.Result);//打印返回值

Console.Read();

}

}

public class MyThread//线程类

{

public int Parame { set; get; }//参数

public int Result { set; get; }//返回值

//构造函数

public MyThread(int parame)

{

this.Parame = parame;

}

//线程执行方法

public void Calculate()

{

Random ra = new Random();//随机数对象

Thread.Sleep(ra.Next(10, 100));//随机休眠一段时间

Console.WriteLine(this.Parame);

this.Result = this.Parame * ra.Next(10, 100);

}

}

(4)使用匿名方法(常用)

使用匿名方法启动线程可以有多个参数和返回值,而且使用非常方便!
复制代码 代码如下:
class Program

{

static void Main(string[] args)

{

int Parame = 100;//当做参数

int Result = 0;//当做返回值

//匿名方法

ThreadStart threadStart = new ThreadStart(delegate()

{

Random ra = new Random();//随机数对象

Thread.Sleep(ra.Next(10, 100));//随机休眠一段时间

Console.WriteLine(Parame);//输出参数

Result = Parame * ra.Next(10, 100);//计算返回值

});

Thread thread = new Thread(threadStart);

thread.Start();//多线程启动匿名方法

//等待线程结束

while (thread.ThreadState != ThreadState.Stopped)

{

Thread.Sleep(10);

}

Console.WriteLine(Result);//打印返回值

Console.Read();

}

}

(5)使用委托开启多线程(多线程深入)

1、用委托(Delegate)的BeginInvoke和EndInvoke方法操作线程
复制代码 代码如下:
BeginInvoke方法可以使用线程异步地执行委托所指向的方法。然后通过EndInvoke方法获得方法的返回值(EndInvoke方法的返回值就是被调用方法的返回值),或是确定方法已经被成功调用。

class Program

{

private delegate int NewTaskDelegate(int ms);

private static int newTask(int ms)

{

Console.WriteLine("任务开始");

Thread.Sleep(ms);

Random random = new Random();

int n = random.Next(10000);

Console.WriteLine("任务完成");

return n;

}

static void Main(string[] args)

{

NewTaskDelegate task = newTask;

IAsyncResult asyncResult = task.BeginInvoke(2000, null, null);

//EndInvoke方法将被阻塞2秒

int result = task.EndInvoke(asyncResult);

Console.WriteLine(result);

Console.Read();

}

}

2、使用IAsyncResult.IsCompleted属性来判断异步调用是否完成
复制代码 代码如下:
class Program

{

private delegate int NewTaskDelegate(int ms);

private static int newTask(int ms)

{

Console.WriteLine("任务开始");

Thread.Sleep(ms);

Random random = new Random();

int n = random.Next(10000);

Console.WriteLine("任务完成");

return n;

}

static void Main(string[] args)

{

NewTaskDelegate task = newTask;

IAsyncResult asyncResult = task.BeginInvoke(2000, null, null);

//等待异步执行完成

while (!asyncResult.IsCompleted)

{

Console.Write("*");

Thread.Sleep(100);

}

// 由于异步调用已经完成,因此, EndInvoke会立刻返回结果

int result = task.EndInvoke(asyncResult);

Console.WriteLine(result);

Console.Read();

}

}

3、使用WaitOne方法等待异步方法执行完成

WaitOne的第一个参数表示要等待的毫秒数,在指定时间之内,WaitOne方法将一直等待,直到异步调用完成,并发出通知,WaitOne方法才返回true。当等待指定时间之后,异步调用仍未完成,WaitOne方法返回false,如果指定时间为0,表示不等待,如果为-1,表示永远等待,直到异步调用完成。
复制代码 代码如下:
class Program

{

private delegate int NewTaskDelegate(int ms);

private static int newTask(int ms)

{

Console.WriteLine("任务开始");

Thread.Sleep(ms);

Random random = new Random();

int n = random.Next(10000);

Console.WriteLine("任务完成");

return n;

}

static void Main(string[] args)

{

NewTaskDelegate task = newTask;

IAsyncResult asyncResult = task.BeginInvoke(2000, null, null);

//等待异步执行完成

while (!asyncResult.AsyncWaitHandle.WaitOne(100, false))

{

Console.Write("*");

}

int result = task.EndInvoke(asyncResult);

Console.WriteLine(result);

Console.Read();

}

}

4、使用回调方式返回结果

要注意的是“my.BeginInvoke(3,300, MethodCompleted, my)”,BeginInvoke方法的参数传递方式:

前面一部分(3,300)是其委托本身的参数。

倒数第二个参数(MethodCompleted)是回调方法委托类型,他是回调方法的委托,此委托没有返回值,有一个IAsyncResult类型的参数,当method方法执行完后,系统会自动调用MethodCompleted方法。

最后一个参数(my)需要向MethodCompleted方法中传递一些值,一般可以传递被调用方法的委托,这个值可以使用IAsyncResult.AsyncState属性获得。
复制代码 代码如下:
class Program

{

private delegate int MyMethod(int second, int millisecond);

//线程执行方法

private static int method(int second, int millisecond)

{

Console.WriteLine("线程休眠" + (second * 1000 + millisecond) + "毫秒");

Thread.Sleep(second * 1000 + millisecond);

Random random = new Random();

return random.Next(10000);

}

//回调方法

private static void MethodCompleted(IAsyncResult asyncResult)

{

if (asyncResult == null || asyncResult.AsyncState == null)

{

Console.WriteLine("回调失败!!!");

return;

}

int result = (asyncResult.AsyncState as MyMethod).EndInvoke(asyncResult);

Console.WriteLine("任务完成,结果:" + result);

}

static void Main(string[] args)

{

MyMethod my = method;

IAsyncResult asyncResult = my.BeginInvoke(3,300, MethodCompleted, my);

Console.WriteLine("任务开始");

Console.Read();

}

}

5、其他组件的BeginXXX和EndXXX方法

在其他的.net组件中也有类似BeginInvoke和EndInvoke的方法,如System.Net.HttpWebRequest类的BeginGetResponse和EndGetResponse方法。其使用方法类似于委托类型的BeginInvoke和EndInvoke方法,例如:
复制代码 代码如下:
class Program

{

//回调函数

private static void requestCompleted(IAsyncResult asyncResult)

{

if (asyncResult == null || asyncResult.AsyncState==null)

{

Console.WriteLine("回调失败");

return;

}

HttpWebRequest hwr = asyncResult.AsyncState as HttpWebRequest;

HttpWebResponse response = (HttpWebResponse)hwr.EndGetResponse(asyncResult);

StreamReader sr = new StreamReader(response.GetResponseStream());

string str = sr.ReadToEnd();

Console.WriteLine("返回流长度:"+str.Length);

}

static void Main(string[] args)

{

HttpWebRequest request =

(HttpWebRequest)WebRequest.Create("http://pletedEventArgs e)

{

MessageBox.Show(null, "工作线程完成!", "提示");

}

4.线程池

(1)线程池的作用

许多时候,我们需要用多线程,但是又不希望线程的数量过多,这就是线程池的作用,.Net为我们提供了现成的线程池ThreadPool。

(2)线程池的使用
复制代码 代码如下:
class Program

{

//线程方法

public static void ThreadProc(object i)

{

Console.WriteLine(i.ToString());

Thread.Sleep(1000);

}

public static void Main()

{

ThreadPool.SetMaxThreads(3, 3);//设置线程池

for (int i = 0; i < 10; i++)

{

ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc), "线程" + i);

}

Console.WriteLine("运行结束");

Console.Read();

}

}

每一个进程都有一个线程池,线程池的默认大小是25,我们可以通过SetMaxThreads方法来设置其最大值。

注意:因为WaitCallback委托的原型是void WaitCallback(object state),那没有办法,我们只能将多个参数封装到一个Object中。

5.线程同步

(1)代码块同步(Monitor与lock )

1、说明:

使用Monitor类的使用与lock关键字的使用在实现原理上相同。

Monitor.Enter 方法:在指定对象上获取排他锁。

Monitor.TryEnter 方法:试图获取指定对象的排他锁。

Monitor.Exit 方法:释放指定对象上的排他锁。

Monitor.Wait 方法:释放对象上的锁并阻塞当前线程,直到它重新获取该锁。

Monitor.Pulse 方法:通知等待队列中的线程锁定对象状态的更改。

Monitor.PulseAll 方法:通知所有的等待线程对象状态的更改。

2、示例
复制代码 代码如下:
class Program

{

private int Count = 0;

//线程执行方法

public void ThreadProc()

{

Monitor.Enter(this);

Thread.Sleep(200);

Count++;

Console.WriteLine(Count);

Monitor.Exit(this);

//等同于

//lock (this)

//{

// Thread.Sleep(200);

// Count++;

// Console.WriteLine(Count);

//}

}

public static void Main()

{

Program p = new Program();

for (int i = 0; i < 100; i++)

{

Thread t = new Thread(p.ThreadProc);

t.Start();

}

Console.Read();

}

}

(2)WaitHandle介绍

WaitHandle是一个抽象类,下面是从它继承来的几个类:

Mutex:一个同步基元,也可用于进程间同步。

AutoResetEvent:通知正在等待的线程已发生事件。无法继承此类。

ManualResetEvent:通知一个或多个正在等待的线程已发生事件。无法继承此类。

WaitHandle的几个方法:

WaitAll:等待指定数组中的所有元素收到信号。

WaitAny:等待指定数组中的任一元素收到信号。

WaitOne:当在派生类中重写时,阻塞当前线程,直到当前的WaitHandle收到信号。

(3)使用Mutex

1、使用Mutex控制线程同步
复制代码 代码如下:
class Program

{

private static Mutex mutex;

static void Main(string[] args)

{

mutex = new Mutex(false);

for (int i = 0; i < 10; i++)

{

Thread thread = new Thread(Method);

thread.Start("线程" + i);

}

Console.WriteLine("主线程执行完毕");

Console.ReadLine();

}

//线程执行方法

private static void Method(Object o)

{

mutex.WaitOne();//等待信号

for (int i = 0; i < 3; i++)

{

Thread.Sleep(500);

Console.WriteLine(o.ToString() + "循环" + i);

}

mutex.ReleaseMutex();//释放信号

}

}

注意:对于WaitAll、WaitAny、WaitOne方法的使用请参考AutoResetEvent与ManualResetEvent。

2、使用Mutex控制进程间的同步
复制代码 代码如下:
class Program

{

static void Main(string[] args)

{

bool flag = false;

Mutex mutex = new Mutex(true, "Test", out flag);

//第一个参数:true--给调用线程赋予互斥体的初始所属权

//第一个参数:互斥体的名称

//第三个参数:返回值,如果调用线程已被授予互斥体的初始所属权,则返回true

if (flag)

{

Console.Write("进程运行...");

}

else

{

Console.Write("这个进程正在运行!");

Thread.Sleep(5000);//线程挂起5秒钟

Environment.Exit(1);//退出程序

}

Console.ReadLine();

}

}

运行以上代码生成的应用程序第一个实例,会得到结果:进程运行...

保持第一个运行状态,运行第二个实例,得到结果:这个进程正在运行!

注意:以上代码中创建了一个mutex,从其参数的解释中得知,第一个调用线程将得到互斥体的初始所属权,如果不释放的话,其他的线程得不到互斥体所有权

(4)使用AutoResetEvent

1、使用WaitAll静态方法

理解AutoResetEvent.WaitAll(Waits)静态方法:WaitAll静态方法就是阻塞当前线程,直到Waits数组里的所有元素都调用Set()方法发送信号,再继续执行当前线程。
复制代码 代码如下:
class Program

{

public static void Main()

{

AutoResetEvent[] Waits = new AutoResetEvent[10];

for (int i = 0; i < 10; i++)

{

int temp = i;

Waits[temp] = new AutoResetEvent(false);

Action thread = delegate()

{

//线程执行方法

Console.WriteLine("线程:" + temp);

Thread.Sleep(1000);

Waits[temp].Set();//发送线程执行完毕信号

};

ThreadStart ts = new ThreadStart(thread);

Thread t = new Thread(ts);

t.Start();

}

AutoResetEvent.WaitAll(Waits);//等待Waits中的所有对象发出信号

Console.WriteLine("线程全部执行完毕!");

Console.Read();

}

}

2、使用WaitAny静态方法

理解AutoResetEvent.WaitAny(Waits)静态方法:WaitAny静态方法就是阻塞当前线程,只要Waits数组有一个元素调用Set()方法发送信号,就继续执行当前线程。
复制代码 代码如下:
class Program

{

public static void Main()

{

AutoResetEvent[] Waits = new AutoResetEvent[10];

for (int i = 0; i < 10; i++)

{

Waits[i] = new AutoResetEvent(false);//初始化Waits

}

for (int i = 0; i < 10; i++)

{

int temp = i;

Action thread = delegate()

{

if (temp > 0)

{

AutoResetEvent.WaitAny(Waits);//等待上一个线程执行完毕

}

//线程执行方法

Thread.Sleep(1000);

Waits[temp].Set();//发送线程执行完毕信号

Console.WriteLine("线程:" + temp+"执行完毕");

};

ThreadStart ts = new ThreadStart(thread);

Thread t = new Thread(ts);

t.Start();

}

Console.Read();

}

}

3、使用WaitOne成员方法

理解Wait.WaitOne()成员方法:WaitOne方法就是阻塞当前线程,只要Wait对象调用了Set()方法发送信号,就继续执行当前线程。
复制代码 代码如下:
class Program

{

public static void Main()

{

AutoResetEvent Wait = new AutoResetEvent(false);

for (int i = 0; i < 10; i++)

{

Action thread = delegate()

{

//线程执行方法

Thread.Sleep(1000);

Wait.Set();//发送线程执行完毕信号

Console.WriteLine("线程:" + i+"执行完毕");

};

ThreadStart ts = new ThreadStart(thread);

Thread t = new Thread(ts);

t.Start();

Wait.WaitOne();//等待调用 Waits.Set()

}

Console.Read();

}

}

(5)使用ManualResetEvent与AutoResetEvent的区别

1、ManualResetEvent与AutoResetEvent的相同点

对于WaitAll、WaitAny、WaitOne方法的使用ManualResetEvent与AutoResetEvent对象是没有区别的。

2、ManualResetEvent与AutoResetEvent的区别

但是对于Set()方法AutoResetEvent只会给一个线程发送信号,而ManualResetEvent会给多个线程发送信号。在我们需要同步多个线程的时候,就只能采用ManualResetEvent了。至于深层次的原因是,AutoResetEvent在Set()之后,会将线程状态自动置为false,而ManualResetEvent在Set()后,线程的状态就变为true了,必须手动ReSet()之后,才会重新将线程置为false。这也就是为什么他们的名字一个为Auto(自动),一个为Manual(手动)的原因。
复制代码 代码如下:
class Program

{

private static ManualResetEvent Wait = new ManualResetEvent(false);

public static void Main()

{

Wait.Set();//设置线程状态为允许执行

Thread thread1 = new Thread(Method);

thread1.Start("线程1");

Thread.Sleep(1000);//等待线程1执行

Wait.Reset();//必须手动复位线程状态,使状态为不允许执行

Thread thread2 = new Thread(Method);

thread2.Start("线程2");//线程2将会一直等待信号

Console.WriteLine("主线程结束");

Console.Read();

}

//线程执行方法

private static void Method(Object o)

{

Wait.WaitOne();//等待信号

Console.WriteLine(o.ToString());

}

}

(6)使用Interlocked进行原子操作

Interlocked类为多个线程共享的变量提供原子操作。

原子操作:Interlocked.Increment()操作是一个原子操作,作用是:Count++ 。原子操作,就是不能被更高等级中断抢夺优先的操作。由于操作系统大部分时间处于开中断状态,所以,一个程序在执行的时候可能被优先级更高的线程中断。而有些操作是不能被中断的,不然会出现无法还原的后果,这时候,这些操作就需要原子操作。就是不能被中断的操作。
复制代码 代码如下:
class Program

{

private static int Count = 0;

static void Main(string[] args)

{

for (int i = 0; i < 100; i++)

{

Thread thread = new Thread(Method);

thread.Start("线程" + i);

}

Thread.Sleep(1000 * 3);//休眠足够的时间等待所有线程执行完毕

Console.WriteLine("操作后的结果:" + Program.Count);

Console.ReadLine();

}

//线程执行方法

private static void Method(Object o)

{

Thread.Sleep(500);

//原子操作,类似:Program.Count++

Interlocked.Increment(ref Program.Count);

//Program.Count++;//非原子操作

Console.WriteLine(o.ToString());

}

}

(7)使用ReaderWriterLock

使用Monitor或Mutex进行同步控制的问题:由于独占访问模型不允许任何形式的并发访问,这样的效率总是不太高。许多时候,应用程序在访问资源时是进行读操作,写操作相对较少。为解决这一问题,C#提供了System.Threading.ReaderWriterLock类以适应多用户读/单用户写的场景。该类可实现以下功能:如果资源未被写操作锁定,那么任何线程都可对该资源进行读操作锁定,并且对读操作锁数量没有限制,即多个线程可同时对该资源进行读操作锁定,以读取数据。如果资源未被添加任何读或写操作锁,那么一个且仅有一个线程可对该资源添加写操作锁定,以写入数据。简单的讲就是:读操作锁是共享锁,允许多个线程同时读取数据;写操作锁是独占锁,同一时刻,仅允许一个线程进行写操作。

ReaderWriterLock类:定义支持单个写线程和多个读线程的锁。

ReaderWriterLockSlim类:表示用于管理资源访问的锁定状态,可实现多线程读取或进行独占式写入访问。
复制代码 代码如下:
class Program

{

private static int Count = 0;//资源

static ReaderWriterLock rwl = new ReaderWriterLock();//读、写操作锁

static void Main(string[] args)

{

for (int i = 0; i < 10; i++)

{

Thread thread = new Thread(Read);//读线程

thread.Start("线程" + i);

}

for (int i = 0; i < 10; i++)

{

Thread thread = new Thread(Write);//写线程

thread.Start("--线程" + i);

}

Console.ReadKey();

}

private static void Read(Object o)//读数据

{

rwl.AcquireReaderLock(1000 * 20); //申请读操作锁,在20s内未获取读操作锁,则放弃

Console.WriteLine(o.ToString() + "读取数据:" + Program.Count);

Thread.Sleep(500);

rwl.ReleaseReaderLock();//释放读操作锁

}

private static void Write(Object o)//写数据

{

rwl.AcquireWriterLock(1000 * 20);//申请写操作锁,在20s内未获取写操作锁,则放弃

Thread.Sleep(500);

Console.WriteLine(o.ToString() + "写数据:" + (++Program.Count));

rwl.ReleaseWriterLock();//释放写操作锁

}

}

(8)使用Semaphore

Semaphore类:限制可同时访问某一资源或资源池的线程数。
复制代码 代码如下:
class Program

{

private static Semaphore semaphore = new Semaphore(0, 5);//初始化信号量

static void Main(string[] args)

{

for (int i = 0; i < 10; i++)

{

Thread thread = new Thread(Method);

thread.Start("线程" + i);

}

semaphore.Release(2);//释放信号量2个

Console.WriteLine("主线程运行完毕!");

Console.Read();

}

//线程执行方法

private static void Method(object o)

{

semaphore.WaitOne();//等待信号量

Thread.Sleep(1000);

Console.WriteLine(o.ToString());

semaphore.Release();//释放信号量

}

}

其它的线程只有等到主线程释放才会执行,因为我给信号量计数器的初始值是0,所以其它线程在主线程释放前都会被阻塞。而后,我在主线程直接用Release(2)函数将计数器置为2,所以2个线程可以同时得到执行。

注意:可以给信号量设置一个名称,这个名称是操作系统可见的,因此,可以使用这些信号量来协调跨进程边界的资源使用。
复制代码 代码如下:
class Program

{

static void Main(string[] args)

{

//初始信号量5个,最多信号量10个

Semaphore seamphore = new Semaphore(5, 10, "Test");

seamphore.WaitOne();//等待信号

Console.WriteLine("获取信号量 1");

seamphore.WaitOne();//等待信号

Console.WriteLine("获取信号量 2");

seamphore.WaitOne();//等待信号

Console.WriteLine("获取信号量 3");

Console.WriteLine("主线程运行完毕!");

Console.Read();

}

}

运行两个这样的程序,结果如下,在第二个运行的示例中,会将阻塞在第三个信号量上:



6.定时器Timer

(1)常用的3个Timer类

System.Threading.Timer 提供以指定的时间间隔执行方法的机制。无法继承此类。

System.Timers.Timer 在应用程序中生成定期事件。

System.Windows.Forms.Timer 实现按用户定义的时间间隔引发事件的计时器。此计时器最宜用于Windows窗体应用程序中,并且必须在窗口中使用。

(2)System.Timers.Timer的使用示例
复制代码 代码如下:
class Program

{

static void Main(string[] args)

{

System.Timers.Timer t = new System.Timers.Timer(1000);//产生事件的时间间隔1s

t.Elapsed += new ElapsedEventHandler(Method); //到达时间的时候执行事件

t.AutoReset = true;//设置是执行一次(false)还是一直执行(true)

t.Enabled = true;//是否执行System.Timers.Timer.Elapsed事件

Console.WriteLine("完成!");

Console.Read();

}

private static void Method(object source,ElapsedEventArgs e)

{

Console.WriteLine("时间:"+e.SignalTime);

}

}

声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。

相关文章