1.概述
使用线程有几个原因。假设从应用程序中进行网络调用需要一定的时间。我们不希望用户界面停止响应。让用户一直等待从服务器返回一个响应。用户同时执行其他一些操作,过着甚至取消发送给服务器的请求。这些都可以使用线程来实现。
对于所有的需要等待操作,列如,因为文件,数据库或网络访问都需要一定的时间,此时就可以启动一个新线程,同时完成其他任务。即使是处理密集型的任务,线程也是有帮助的。 一个进程的多线程可以同时在不同CPU上运行,或多核CPU的不同内核上。
如果多线程同时运行,访问相同的数据,就很容易出现问题,必须实现同步机制。
下面介绍的主要命名空间是:Syetem.Threading和System.Threading.Tasks
线程是程序中独立的指令流。,
运行在服务器上的应用程序中,等待客户请求的线程,称为侦听器线程。 只要接受到请求,就把它传递给另一个工作线程,之后继续与客户同行。侦听器线程会立即返回,接受下一个客户发送的下一个请求。
进程包含资源,每个进程都分配了虚拟内存。一个进程至少包含一个线程。操作系统会调用线程。线程有一个优先级,实际上正在处理的程序的位置计数器,一个存储器局部变量的栈。每个线程都有自己的栈,但程序代码的内存和堆由一个进程的所有线程共享。 这使一个进程的所有线程之间的通信非常快–该进程的所有线程都寻址相同的虚拟内存。但是,这也使处理比较困难,因为多线程可以修改同一个内存位置。
线程是运行程序所必须的。在.NET4.0之前,必须直接使用Thread类和ThreadPool类。现在,.NET对着两个类做了抽象,允许使用Parallel类和Task类。
为编写能够利用并行性的代码,必须区分两种主要的场景:任务并行性和数据并行性。
- 任务并行性:使用CUP的代码被并行化。CPU的多个核心会被利用起来,更加快速的完成包含多个任务的活动,而不是在一个核心中按顺序一个一个地执行任务。
- 数据并行性:使用数据集合,在集合上执行的工作被划分为多个任务
两则可以混合起来使用。
Parallel类
Parallel类是对线程的一个很好的抽象。该类位于System.Threading.Tasks命名空间中,提供了数据和任务并行性
Parallel类定义了并行的for和foreach的静态方法。对于C#的for和foreach语句而言,循环从一个线程中运行。Parallel类使用多个任务,因此使用多个线程来完成作业。
Parallel.For()和Parallel.ForEach()方法在每次迭代中调用相同的代码,而Parallel.Invoke()方法允许同时调用不同的方法。Parallel.Invoke用于任务并行性,而Parallel.ForEach用于数据并行性。
ParallelLoopResult result = Parallel.For(0, 10, i => { Console.WriteLine("{0},task:{1},thread:{2}", i , Task.CurrentId, Thread.CurrentThread.ManagedThreadId); Thread.Sleep(10); // Task.Delay(100);//是一个异步方法,用于释放线程供其他任务使用。 }); Console.WriteLine("IS complete:{0}", result.IsCompleted); Console.ReadKey();
程序的执行依次是:0-2-3-1-4-6-8-7-5-9;共有五个任务和五个线程。任务不一定映射到一个线程上,线程也可以被不同的任务重用。
在4.5中新增的Thread.Sleep方法,而不是Task.Delay方法。Task.Delay是一个异步方法,用于释放线程提供其他任务使用。下面代码使用await关键字,所以一旦延迟就会调用。
ParallelLoopResult asyncResult = Parallel.For(0, 10, async i => { Console.WriteLine("{0},task:{1},thread:{2}", i , Task.CurrentId, Thread.CurrentThread.ManagedThreadId); //使用await关键字,所以一旦完成延迟,就会立即调用这些代码 // 延迟后执行的代码和延迟前执行的代码可以运行不用的线程中 await Task.Delay(100); Console.WriteLine("{0},task:{1},thread:{2}", i , Task.CurrentId, Thread.CurrentThread.ManagedThreadId); }); Console.WriteLine("IS complete:{0}", asyncResult.IsCompleted); Console.ReadKey();
在代用Thread.Delay方法后,线程发生了变化。在输出中还可以看到,任务不再存在,只有线程留下了,而且这里重用了前面的线程。另一重要方面,Parallel类的For方法并没有等待延迟,而是直接完成。Parallel类只等待它创建的任务,而不是等待其他后台活动。在延迟后,也有可能完全看不到方法的输出,出现这种情况的原因是主线程(前台线程)结束,所有的后台线程被终止。
提前停止Parallel.For
也可以提前中断Parallel.For()方法吗,而不是完成所有迭代。For()方法有一个重载接受Action
ParallelLoopResult asyncResult = Parallel.For(10, 40, async (int i,ParallelLoopState pls ) => { Console.WriteLine("{0},task:{1},thread:{2}", i , Task.CurrentId, Thread.CurrentThread.ManagedThreadId); //使用await关键字,所以一旦完成延迟,就会立即调用这些代码 // 延迟后执行的代码和延迟前执行的代码可以运行不用的线程中 await Task.Delay(10); if (i > 10) pls.Break(); //Console.WriteLine("{0},task:{1},thread:{2}", i // , Task.CurrentId, Thread.CurrentThread.ManagedThreadId); }); Console.WriteLine("IS complete:{0}", asyncResult.IsCompleted); Console.WriteLine("lowest break iteration :{0}", asyncResult.LowestBreakIteration); Console.ReadKey();
2.2使用Parallel.ForEach()方法循环
Parallel.ForEach()方法遍历实现了IEnumerable的集合,其方式类似于foreach语句,但以异步方法遍历。
string[] data = { "aaa", "bbb", "ccc", "ddd", "eee", "fff", "asss" }; ParallelLoopResult resule = Parallel.ForEach<string>(data, s => { Console.WriteLine(s); });
如果需要中断循环,使用ForEach()方法重载和ParallelLoopState 参数。
ForEach()方法重载:
public static ParallelLoopResult ForEach<TSource>(IEnumerable<TSource> source, Action<TSource, ParallelLoopState, long> body)
实现代码如下:
string[] data = { "aaa", "bbb", "ccc", "ddd", "eee", "fff", "asss" }; ParallelLoopResult resule = Parallel.ForEach<string>(data,(s,pls,l) => { if (s.Contains("bbb")) pls.Break(); Console.WriteLine(s); Console.WriteLine( "LowestBreakIteration:" + pls.LowestBreakIteration); }); Console.ReadKey();
2.3通过Parallel.Invoke()方法调用多个方法
如果多个任务并行运行,就可以使用parallel.Invoke()方法,它提供了任务并行模式。Parallel.Invoke()方法允许传递一个Action委托数组,在其中可以指定运行的方法。并行调用Foo和Bar方法
static void ParallelInvoke() { Parallel.Invoke(Foo,Bar); } static void Foo() { Console.WriteLine("foo"); } static void Bar() { Console.WriteLine("Bar"); }
Parallel类使用起来十分方便,而且可以使用任务,已可以用于数据并行性。如果想要更细致的控制,并且不想等待Parallel类结束后才开始动作,就可以使用Task类,当然,结合使用Task类和Parallel类也是可以的