C# 多线程
一、线程之间共享变量:
- 同对象中的变量(线程内部方法)
class ThreadTest { bool done; static void Main() { ThreadTest tt = new ThreadTest(); // Create a common instance new Thread(tt.Go).Start(); tt.Go(); } // Note that Go is now an instance method void Go() { if (!done) { done = true; Console.WriteLine("Done"); } } }
- 静态变量
class ThreadTest { static bool done; // Static fields are shared between all threads static void Main() { new Thread(Go).Start(); Go(); } static void Go() { if (!done) { done = true; Console.WriteLine("Done"); } } }
二、Join & Sleep
- Join 等待该线程执行完成之后。
- Sleep 暂停当前线程一段时间,Sleep(0) & Yield 同效,将当前进程置为等待状态,让出 CPU 给其他线程。
三、传递参数
- 匿名函数
Thread t = new Thread ( () => Print ("Hello from t!") ); new Thread (() => {...}).Start(); new Thread (delegate(){...}).Start();
- Start 函数
Thread t = new Thread (Print).Start ("Hello from t!"); static void Print (object messageObj) //函数入参只能为 object 类型
- 注意参数的变化
for (int i = 0; i < 10; i++) new Thread (() => Console.Write (i)).Start(); Output: 0223557799
四、线程命名
- 命名当前线程
Thread.CurrentThread.Name = "main";
- 命名其他线程
Thread worker = new Thread (Go); worker.Name = "worker";
五、前台线程和后台线程 (Foreground Threads & Background Threads)
- 前台线程可以阻止程序退出。除非所有前台线程都结束,否则 CLR不会关闭程序。(自己创建的线程默认前台)
- 后台线程有时候也叫 Daemon Thread 。他被 CLR 认为是不重要的执行路径,可以在任何时候舍弃。因此当所有的前台线程结束,即使还有后台线程在执行, CLR 也会关闭程序。(线程池中的线程默认后台)
线程的前后台与线程的优先级无关。
某些时候需要等待后台线程执行完成之后退出程序,如释放资源,可如下方式实现:
- 如果线程是自己创建的,可以使用 Join 函数
- 如果线程是线程池中的,可以使用 Wait 事件
六、线程优先级
enum ThreadPriority { Lowest, BelowNormal, Normal, AboveNormal, Highest }
提升线程的优先级需要仔细认真的考虑,因为可能造成资源的死锁。
线程的优先级是在进程的优先级之下的,如果A进程中的A1线程想与B进程中B1线程争夺资源,首先应提高A进程的优先级:
using (Process p = Process.GetCurrentProcess()) p.PriorityClass = ProcessPriorityClass.High;
ProcessPriorityClass.High为进程的最高等级,如果你的进程设置为 High,并且进入了无限循环,操作系统将会被锁定。由于这个原因,High 一般用于实时系统 Realtime,改进程在进入时间轮片之后,将不会出让 CPU 给任何进程。
七、异常处理
异常处理try/catch/finally需要写在函数内部,否则会导致主程序的崩溃,程序直接退出。
八、线程池
通过Tread创建线程需要消耗时间来组织资源创建线程,并且会消耗内存。使用线程池的线程不会有这些问题。进入线程池方式:
- Task Parallel Library(TPL .NET4.0+)
- 调用 ThreadPool.QueueUserWorkItem
- 异步委托(Asynchronous delegates)
- 通过BackgroundWorker
下列间接使用线程池构建:
- WCF、Remoting、ASP.NET、ASMX Web Service 服务
- System.Timers.Timer & System.Threading.Timer
- 框架方法,如WebClient、大部分的BeginXXX等异步方法
- PLINQ
使用线程池需注意:
- 线程池中的线程不可命名,调试困难
- 线程池中的线程均为后台线程
- 在应用程序生命周期的早期,阻塞线程可能会导致额外的延迟。
查询当前线程是否在线程池中:Thread.CurrentThread.IsThreadPoolThread.
九、使用线程池
- Task Parallel Library(TPL .NET4.0+)
Task.Factory.StartNew (Go, arg1, arg2…);
Go的参数可以不为object类型
接收返回参数Task<TResult>:
Task<string> task = Task.Factory.StartNew<string>(() => DownloadString ("http://www.linqpad.net")); string result = task.Result; //use the result, block the main thread until the task finished
当使用返回参数时,相当于在调用参数之前调用了 task.Wait();
- 调用 ThreadPool.QueueUserWorkItem
当使用.NET为4.0之前版本时,我们只能使用ThreadPool.QueueUserWorkItem和Asynchronous delegates两种方式。
- 异步委托(Asynchronous delegates)
与ThreadPool.QueueUserWorkItem区别是:Asynchronous delegates可以接收返回结果,并且可以在主线程中统一处理异常。
- 实例化委托
- 调用委托的BeginInvoke,使用IAsyncResult接收返回值
- 需要使用返回结果时,调用委托的EndInvoke,并且传递已保存的IAsyncResult对象。
EndInvoke做的三件事:
- 等待异步委托执行完成。
- 接收返回结果(包括ref & out 参数)
- 抛出异常到主线程。
拆分实现:
method在BeginInvoke时传递,通过IAsyncResult.AsyncState传递。其中IAsyncResult.AsyncState为object类型,可以传递任何变量。