线程池
创建线程需要时间,如果有不同的小任务要完成,就可以事先创建许多线程,在应完成这些任务时发出请求。这个线程数最好在需要更多线程时增加,在需要释放资源时减少。
不需要自己创建这样的一个列表。该列表由ThreadPool类托管。该类会在需要时增加线程池中线程数,直到最大的线程数。
- 可以指定创建线程池时立即启动的最小线程数,以及线程池中可用的最大线程数。
- 如果更多的作业要处理,线程池中的线程个数也到了极限,最新的作业就要排队,且必须等待线程完成其作业。
- 线程池中的线程都是后台线程,不能把入池的线程改为前台线程。
- 不能给入池的线程设置优先级和名字。
- 对于COM对象,入池的所有线程都是多线程单元线程。许多COM对象都需要单线程单元线程。
- 入池的线程只能用于时间比较短的任务。如果线程要一直运行,就应该使用Thread类创建一个线程。
static void Main() { int nWorkerThreads; int nCompletionPorThreads; ThreadPool.GetMaxThreads(out nWorkerThreads, out nCompletionPorThreads); //ThreadPool.SetMaxThreads(500, 500); lib.print("Max worker threads : " + nWorkerThreads); lib.print("I/O completion threads: " + nCompletionPorThreads); for(int i=0; i<10; i++) { ThreadPool.QueueUserWorkItem(JobForAThread);//将方法排入队列以便执行。 此方法在有线程池线程变得可用时执行。 } Thread.Sleep(3000); } static void JobForAThread(object state) { for(int i = 0; i<3; i++) { Console.WriteLine("loop {0}, running inside pooled thread {1}", i, Thread.CurrentThread.ManagedThreadId); Thread.Sleep(50); } }
示例应用程序首先要读取工作线程和I/O线程的最大线程数,把这些信息写入控制台中。接着在for循环中,调用Thread.QueueuserWorkItm()方法,传递一个WaitCallback类型的委托,把JobForAThread()方法赋予线程池中的线程。
线程池收到这个请求后,就会从池中选择一个线程,来调用该方法。如果线程池还没有运行,就会创建一个线程池,并启动第一个线程。如果线程池己经在运行,且有一个空闲线程来完成该任务,就把该作业传递给这个线程。
异步委托
创建线程最简单的方式是定义一个委托,然后异步调用它。
委托使用线程池完成异步任务,当没有前台线程运行时,异步委托将直接结束。
static void Main(string[] args) { Action action = new Action(() => { for(int i=0; i<100; i++) { lib.put("."); Thread.Sleep(10); } }); AsyncCallback calback = (IAsyncResult result) => { Thread.Sleep(100); lib.print(result.AsyncState); }; var rs = action.BeginInvoke(calback, "Begin Invoke");//如此,便启动了异步委托。下面用不同方法等待异步委托完成。 //方法一,用EndInvoke方法,该方法会一直等待,直到委托完成任务为止。 action.EndInvoke(rs); //方法二,使用IAsyncResult相关联的等待句柄。使用AsyncWaitHandle属性可以访问等待句柄。 //这个属性返回一个WaitHandle类型对象,它可以等待委托线程完成其任务。参数是最长等待时间, //-1表示无限等待。如果当前实例收到信号,则返回为 true;否则为 false。 rs.AsyncWaitHandle.WaitOne(5000); //方法三,不断检查 while (true) { if (!rs.IsCompleted) { Thread.Sleep(50); } else { break; } } //因为callback最终是异步线程回调的,所以,如果直接退出,callback将无法打印出Begin Invoke。 Thread.Sleep(200); }
任务
System.Threading.Tasks包含的类抽象出了线程功能,在后台使用ThreadPool。任务表示应完成的某个单元工作,这个单元工作可以在单独的线程中运行,也可以以同步方式启动一个任务,这需要等待主线程。使用任务不仅可以获得一个抽象层,还可以对底层线程进行很多控制。
- 启动任务
可以使用实例化的TaskFactory类,在其中把TaskMethod()方法传递给StarNew()方法,就会立刻启动任务。也可以使用Task类的构造函数。实例化Task对象时,任务不会立即运行,而是指定Created状态。接着调用Task类的Start()方法,来启动任务。使用Task类时,还可以调用TunSynchronously()方法。
static void Main(string[] args) { Task t1 = new Task(DoOnFirst); t1.Start(); TaskFactory tf = new TaskFactory(); Task t2 = tf.StartNew(DoOnFirst); Task t3 = Task.Factory.StartNew(DoOnFirst); Task.WaitAll(t1, t2, t3); } static void DoOnFirst() { lib.print("Task.CurrentId :" + Task.CurrentId); lib.print("-----------"); Thread.Sleep(1000); }
- 连续的任务
连续任务通过在任务上调用ContinueWith()方法类定义。不带TaskContinuationOptions参数,则无论前一个任务是如何结束的,后续任务都启动。也可以用TaskContinuationOptions枚举中的值,来指定连续任务在什么情况下启动。
static object o = new object(); static void Main(string[] args) { CancellationTokenSource cs = new CancellationTokenSource(); Task t1 = new Task(DoOnFirst, cs.Token); Task t2 = t1.ContinueWith(DoOnSecond, TaskContinuationOptions.OnlyOnRanToCompletion);//t1完成的情况下启动t2 Task t3 = t1.ContinueWith(DoOnThird, TaskContinuationOptions.OnlyOnCanceled);//t1被取消的情况下启动t3 try { t1.Start(); //cs.Cancel(); //打开注释,取消了t1,将执行DoOnThird。 //cs.Token.ThrowIfCancellationRequested(); } catch { lib.print("t1.IsCanceled : " + t1.IsCanceled); } Console.ReadKey(); } static void DoOnFirst() { lock (o) { lib.print("Task.CurrentId :" + Task.CurrentId); lib.print("-----------"); Thread.Sleep(1000); } } static void DoOnSecond(Task t) { lock (o) { lib.print("task " + t.Id + " finished."); lib.print("this task id " + Task.CurrentId); lib.print("-----------"); Thread.Sleep(1000); } } static void DoOnThird(Task t) { lock (o) { lib.print("task " + t.Id + " Canceld."); lib.print("this task id " + Task.CurrentId); lib.print("-----------"); Thread.Sleep(1000); } }
任务层次结构
任务也可以构成一个层次结构。一个任务启动一个新任务时,就启动了一个父/子层次结构。
任务的结果
任务结束时,可以把一些有用的状态信息写到共享对象中。也可以使用返回结果的任务返回这些信息。
static void Main(string[] args) { Task<int> t1 = new Task<int>((object o)=>{return 111;}, ""); t1.Start(); t1.Wait(); lib.print(t1.Result); Task t = new Task(DoParentTask); t.Start(); }