构建多线程应用程序
在.net平台下,应用程序和线程之间不是一一对应的。 在任何时间,一个应用程序域内都可能有多个线程。 而且,一个特定的线程在它的生命周期内并不一定被限定在一个应用程序域中。windows线程调度程序和CLR会根据需要让线程能够自由地跨越应用程序域的边界,但是在任何一个时间点上,一个线程只能运行在一个应用程序域中(也即是说,一个线程同时在多个应用程序域上执行任务是不可能的)
.net 4 TPL(Task Parllel Library 任务并行库),能用最小的代价比较省心地创建额外的线程。
19.7 以编程方式创建次线程
步骤 1.创建一个方法作为新线程的入口点 2.创建一个ParameterizedThreadStart(或者ThreadStart)委托,并把在上一步所定义方法的地址传给委托的构造函数 3.创建一个Thread对象,并把ParameterizedThreadStart或ThreadStart委托作为构造函数的参数。 4.建立任意初始化线程的特性(名称、优先级等)。 5.调用Thread.Start()方法。在第(2)个步骤中建立的委托所指向的方法将在线程中尽快开始执行。
19.8.3 使用System.Threading.Interlocked类型允许我们来原子性操作单个数据。 方法包括 1.CompareExchange() 安全地比较两个值是否相等,如果相等,用第3个值改变第一个值。 2.Decrement() 安全递减1 3.Exchange() 安全地交换数据 4.Increment() 安全递加1
19.9 使用Timer Callback 编程
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace TimerApp { class Program { static void PrintTime(object state) { Console.WriteLine("Time is: {0} {1}", DateTime.Now.ToLongTimeString(), state.ToString()); } static void Main(string[] args) { Console.WriteLine("***** Working with Timer type *****\n"); // Create the delegate for the Timer type. TimerCallback timeCB = new TimerCallback(PrintTime); // Establish timer settings. Timer t = new Timer( timeCB, // The TimerCallback delegate type. "Hello From Main", // Any info to pass into the called method (null for no info). 0, // Amount of time to wait before starting. 1000); // Interval of time between calls (in milliseconds). Console.WriteLine("Hit key to terminate..."); Thread.Sleep(5000); Console.ReadLine(); } } }
19.10 CLR线程池 为了取得更高的效率,委托BeginInvoke()方法创建了由运行时维护的工作者线程池。为了更好的和这些线程进行交互, System.Threading命名空间提供了Threading类类型。 如果想使用池中的工作者线程排队执行一个方法,可以使用ThreadPool.QueueUserWorkItem()方法。这个方法进行了重载, 除了可以传递一个WaitCallback委托之外还可以指定一个可选的表示自定义状态数据的System.Object;
using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace ThreadPoolApp { #region Helper class public class Printer { private object lockToken = new object(); public void PrintNumbers() { lock (lockToken) { // Display Thread info. Console.WriteLine("-> {0} is executing PrintNumbers()", Thread.CurrentThread.Name); // Print out numbers. Console.Write("Your numbers: "); for (int i = 0; i < 10; i++) { Console.Write("{0}, ", i); Thread.Sleep(1000); } Console.WriteLine(); } } } #endregion class Program { static void Main(string[] args) { Console.WriteLine("***** Fun with the CLR Thread Pool *****\n"); Console.WriteLine("Main thread started. ThreadID = {0}", Thread.CurrentThread.ManagedThreadId); Printer p = new Printer(); WaitCallback workItem = new WaitCallback(PrintTheNumbers); // Queue the method 10 times for (int i = 0; i < 10; i++) { ThreadPool.QueueUserWorkItem(workItem, p); } Console.WriteLine("All tasks queued"); Console.ReadLine(); } static void PrintTheNumbers(object state) { Printer task = (Printer)state; task.PrintNumbers(); } } }
比起显示创建Thread对象,使用被CLR维护的线程池的好处是线程池减少了线程创建、开始和停止的次数,而提高了效率,同时能够使我们将注意力放到业务逻辑上而不是多线程架构上。 然而,在某些情况下应优先使用手工线程管理,具体如下: 1.如果需要前台线程或设置优先级别。线程池中的线程总是后台线程,且它的优先级是默认的(ThreadPriority.Normal)。 2.如果需要有一个带有固定标识的线程便于退出、挂起或通过名字发现它。
19.11 .net平台下的并行编程 如果计算机支持多CPU,就能够以并行方式执行线程,这将大大改善应用程序的运行时性能。 .net4 发布了一个全新的并行编程库。使用System.Threading.Tasks中的类型,可以构建细粒度的、可扩展的并行代码,而不必直接与线程和线程池打交道。此外,也可以使用强类型的LINQ查询(通过并行LINQ,即PLINQ)来分配工作。
1.任务并行库API System.THreading.Tasks中的类型(以及System.Threding中的一些相关类型)被称为任务并行库(Task Parallerl Library,TPL)。 TPL使用CLR线程池自动将应用程序的工作动态分配到可用的CPU中,TPL还处理工作分区、线程调度、状态管理和其他低级别的细节操作。最终结果是,你可以最大限度的提升.net应用程序的性能,并且避免直接操作线程带来的复杂性。
使用TPL的第一种方式是执行数据并行。简单说,该术语是指使用Parallel.For()或Parallel.ForEach()方法以并行方式对数组或集合中的数据进行迭代。
任务并行
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; // Need these namespaces! using System.Threading.Tasks; using System.Threading; using System.IO; namespace DataParallelismWithForEach { public partial class MainForm : Form { // New Form level variable. private CancellationTokenSource cancelToken = new CancellationTokenSource(); public MainForm() { InitializeComponent(); } private void btnProcessImages_Click(object sender, EventArgs e) { // Start a new "task" to process the files. Task.Factory.StartNew(() => { ProcessFiles(); }); } private void ProcessFiles() { // Use ParallelOptions instance to store the CancellationToken ParallelOptions parOpts = new ParallelOptions(); parOpts.CancellationToken = cancelToken.Token; parOpts.MaxDegreeOfParallelism = System.Environment.ProcessorCount; // Load up all *.jpg files, and make a new folder for the modified data. string[] files = Directory.GetFiles(@"C:\Users\Public\Pictures\Sample Pictures", "*.jpg", SearchOption.AllDirectories); string newDir = @"C:\ModifiedPictures"; Directory.CreateDirectory(newDir); try { // Process the image data in a parallel manner! Parallel.ForEach(files, parOpts, currentFile => { parOpts.CancellationToken.ThrowIfCancellationRequested(); string filename = Path.GetFileName(currentFile); using (Bitmap bitmap = new Bitmap(currentFile)) { bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone); bitmap.Save(Path.Combine(newDir, filename)); this.Text = string.Format("Processing {0} on thread {1}", filename, Thread.CurrentThread.ManagedThreadId); } } ); this.Text = "All done!"; } catch (OperationCanceledException ex) { this.Text = ex.Message; } } private void btnCancelTask_Click(object sender, EventArgs e) { // This will be used to tell all the worker threads to stop! cancelToken.Cancel(); } } }
19.12 并行LINQ查询(PLINQ)
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.Tasks; using System.Threading; namespace PLINQDataProcessingWithCancellation { public partial class MainForm : Form { private CancellationTokenSource cancelToken = new CancellationTokenSource(); private void btnCancel_Click(object sender, EventArgs e) { cancelToken.Cancel(); } public MainForm() { InitializeComponent(); } private void btnExecute_Click(object sender, EventArgs e) { // Start a new "task" to process the files. Task.Factory.StartNew(() => { ProcessIntData(); }); } private void ProcessIntData() { // Get a very large array of integers. int[] source = Enumerable.Range(1, 10000000).ToArray(); // Find the numbers where num % 3 == 0 is true, returned // in descending order. int[] modThreeIsZero = null; try { modThreeIsZero = (from num in source.AsParallel().WithCancellation(cancelToken.Token) where num % 3 == 0 orderby num descending select num).ToArray(); } catch (OperationCanceledException ex) { this.Text = ex.Message; } MessageBox.Show(string.Format("Found {0} numbers that match query!", modThreeIsZero.Count())); } } }
Thread 类实例级的成员
IsAlive 指示线程是否开始了
Priority 调度优先级
Interrupt() 中断当前线程,唤醒处于等待中的线程。
join() 阻塞调用线程,直到某个(调用Join方法的)线程终止为止。
Resume() 使已经挂起的线程继续执行
Suspend() 挂起当前线程,如果线程已经挂起,则不起作用。 挂起、挂起后激活已经不经常用。
System.Threading.Tasks中的类型被称为任务并行库(Task Parallel Library,TPL)