天天看点

C#线程C#的线程(一)

线程是一个独立的运行单元,每个进程内部都有多个线程,每个线程都可以各自同时执行指令。每个线程都有自己独立的栈,但是与进程内的其他线程共享内存。但是对于.NET的客户端程序(Console,WPF,WinForms)是由CLR创建的单线程(主线程,且只创建一个线程)来启动。在该线程上可以创建其他线程。

图:

C#线程C#的线程(一)

多线程由内部线程调度程序管理,线程调度器通常是CLR委派给操作系统的函数。线程调度程序确保所有活动线程都被分配到合适的执行时间,线程在等待或阻止时 (例如,在一个独占锁或用户输入) 不会消耗 CPU 时间。

在单处理器计算机上,线程调度程序是执行时间切片 — 迅速切换每个活动线程。在 Windows 中, 一个时间片是通常数十毫秒为单位的区域 — — 相比来说 线程间相互切换比CPU更消耗资源。在多处理器计算机上,多线程用一种混合的时间切片和真正的并发性来实现,不同的线程会在不同的cpu运行代码。

如:

在主线程上创建了一个新的线程,该新线程执行WrWrite2方法,在调用t.Start()时,主线程并行,输出“1”。

C#线程C#的线程(一)

线程Start()之后,线程的IsAlive属性就为true,直到该线程结束(当线程传入的方法结束时,该线程就结束)。

在新线程和主线程上调用Go方法时分别创建了变量cycles,这时cycles在不同的线程栈上,所以相互独立不受影响。

C#线程C#的线程(一)

如果不同线程指向同一个实例的引用,那么不同的线程共享该实例。

新线程和主线程上调用了同一个实例的Go方法,所以变量i共享。

静态变量也可以被多线程共享

如果将Go方法的代码位置互换

如果新线程在Write之后,done=true之前,主线程也执行到了write那么就会有两个done。

不同线程在读写共享字段时会出现不可控的输出,这就是多线程的线程安全问题。

解决方法: 使用排它锁来解决这个问题--lock

当多个线程都在争取这个排它锁时,一个线程获取该锁,其他线程会处于blocked状态(该状态时不消耗cpu),等待另一个线程释放锁时,捕获该锁。这就保证了一次

只有一个线程执行该代码。

Join可以实现暂停另一个线程,直到调用Join方法的线程结束。

线程t调用Join方法,阻塞主线程,直到t线程执行结束,再执行主线程。

Sleep:暂停该线程一段时间

上面的例子是使用Thread类的构造函数,给构造函数传入一个ThreadStart委托。来实现的。

然后调用Start方法,来执行该线程。委托执行完该线程也结束。

多数情况下,可以不用new ThreadStart委托。直接在构造函数里传入void类型的方法。

使用lambda表达式

最简单的就是在lambda表达式直接传入参数。

或者在调用Start方法时传入参数

此时传给Thread的构造函数是有一个参数的void类型方法。

因为Thread的构造函数也可以传递如下委托:

Lambda简洁高效,但是在捕获变量的时候要注意,捕获的变量是否共享。

因为每次循环中的i都是同一个i,是共享变量,在输出的过程中,i的值会发生变化。

解决方法-临时变量

这时每个线程都指向新的temp;在该线程中temp不受其他线程影响。

总结:

Thread构造函数传递方法有两种方式:

默认情况下创建的线程都是Foreground,只要有一个Foregournd线程在执行,应用程序就不会关闭。

Background线程则不是。一旦Foreground线程执行完,应用程序结束,background就会强制结束。

可以用IsBackground来查看该线程是什么类型的线程。

此时并不能在Main方法里捕获线程Go方法的异常,如果是Thread自身的异常可以捕获。

正确捕获方式:

当创建一个线程时,就会消耗几百毫秒cpu,创建一些新的私有局部变量栈。每个线程还消耗(默认)约1 MB的内存。线程池通过共享和回收线程,允许在不影响性能的情况下启用多线程。

每个.NET程序都有一个线程池,线程池维护着一定数量的工作线程,这些线程等待着执行分配下来的任务。

线程池线程注意点:

通过Thread.CurrentThread.IsThreadPoolThread.可以查看该线程是否是线程池的线程。

使用线程池创建线程的方法:

Task

ThreadPool.QueueUserWorkItem

Asynchronous delegates

BackgroundWorker

TPL

Framework4.0下可以使用Task来创建线程池线程。调用Task.Factory.StartNew(),传递一个委托

Task.Factory.StartNew 返回一个Task对象。可以调用该Task对象的Wait来等待该线程结束。

给Task构造函数传递Action委托,或对应的方法,调用start方法,启动任务

直接调用Task.Run传入方法,执行。

Task泛型允许有返回值。

QueueUserWorkItem没有返回值。使用 QueueUserWorkItem,只需传递相应委托的方法就行。

委托异步可以返回任意类型个数的值。

使用委托异步的方式:

声明一个和方法匹配的委托

调用该委托的BeginInvoke方法,获取返回类型为IAsyncResult的值

调用EndInvoke方法传递IAsyncResulte类型的值获取最终结果

EndInvoke做了三件事情:

等待委托异步的结束。

获取返回值

抛出未处理异常给调用线程

推荐使用回调函数来简化委托的异步调用,回调函数参数为IAsyncResult类型

}

本文转自zsdnr 51CTO博客,原文链接:http://blog.51cto.com/12942149/1949804,如需转载请自行联系原作者