多线程编程学习笔记——使用并发集合(二)
二、 使用ConcurrentQueue来实现异步处理
本示例将学习如何创建一个能被多个线程异步处理的一组任务的例子。
一、程序示例代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Collections.Concurrent; using System.Diagnostics; using System.Threading; namespace ThreadCollectionDemo { class Program { const string item = "Dict Name"; public static string CurrentItem; static double time1; static void Main(string[] args) { Console.WriteLine(string.Format("----- ConcurrentQueue 操作----")); Task task = TaskRun1(); task.Wait(); Console.Read(); } private static async Task TaskRun1() { var queue = new ConcurrentQueue<CustomTask>(); var cts = new CancellationTokenSource(); var taskSrc = Task.Run(() => TaskProduct(queue)); Task[] process = new Task[4]; for (int i = 1; i <= 4; i++) { string processId = i.ToString(); process[i - 1] = Task.Run(() => TaskProcess(queue, "Processer " + processId, cts.Token)); } await taskSrc; cts.CancelAfter(TimeSpan.FromSeconds(2)); await Task.WhenAll(process); } static async Task TaskProduct(ConcurrentQueue<CustomTask> queue) { for (int i = 0; i < 20; i++) { await Task.Delay(50); var workitem = new CustomTask { Id = i }; queue.Enqueue(workitem); Console.WriteLine(string.Format("把{0} 元素添加到ConcurrentQueue",workitem.Id)); } } static async Task TaskProcess(ConcurrentQueue<CustomTask> queue,string name,CancellationToken token) { CustomTask workitem; bool dequeueSuccesfl = false; await GetRandomDely(); do { dequeueSuccesfl = queue.TryDequeue(out workitem); if (dequeueSuccesfl) { Console.WriteLine(string.Format("元素 {0} 从ConcurrentQueue中取出 ,名称:{1} ", workitem.Id, name)); } await GetRandomDely(); } while (!token.IsCancellationRequested); } static Task GetRandomDely() { int dely = new Random(DateTime.Now.Millisecond).Next(1, 1000); return Task.Delay(dely); } } public class CustomTask { public int Id { get; set; } } }
2.程序运行结果如下图。
当程序运行时,我们使用ConcurrentQueue集合实现创建了一个任务队列。然后创建了一个取消标志,它是用来在我们将任务放入队列后停止工作 的。接下来启动了一个单独的工作线程来将任务放入任务队列中。这部分分为异步处理产生了工作 量。
现在定义这个程序中消费任务的部分。我们创建了四个工作 线程,它们会随机等待一段时间,然后从任务队列中获取一个任务,处理这个任务,一直重复整个过程直到我们发出取消标志信号。最后,我们启动产生任务的线程,等待这个线程完成。然后使用取消标志给消费发信号 我们完成了工作。最后一步将等待所有的消费完成。
我们看到队列中的任务按从前到后的顺序被 处理,但一个后面的任务是有可能会比前面的任务先处理的,因为我们有四个工作 线程独立地运行,而且任务处理时间并不是恒定的。我们看到 访问这个队列是线程安全的,没有一个元素会被提取两次。
三、 改变ConcurrentStack异步处理顺序
本示例是对上一面的示例的修改版。我们又一次创建了被多个工作线程异步处理的一组任务,但是这次使用ConcurrentStack来实现,我们来看看这两个示例会有什么不同。
1. 程序的代码如下图。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Collections.Concurrent; using System.Diagnostics; using System.Threading; namespace ThreadCollectionDemo { class Program { static void Main(string[] args) { Console.WriteLine(string.Format("----- ConcurrentStack 操作----")); Task task = TaskStack(); task.Wait(); Console.Read(); } private static async Task TaskStack() { var stack = new ConcurrentStack<CustomTask>(); var cts = new CancellationTokenSource(); var taskSrc = Task.Run(() => TaskProduct(stack)); Task[] process = new Task[4]; for (int i = 1; i <= 4; i++) { string processId = i.ToString(); process[i - 1] = Task.Run(() => TaskProcess(stack, "Processer " + processId, cts.Token)); } await taskSrc; cts.CancelAfter(TimeSpan.FromSeconds(2)); await Task.WhenAll(process); } static async Task TaskProduct(ConcurrentStack<CustomTask> stack) { for (int i = 0; i < 20; i++) { await Task.Delay(50); var workitem = new CustomTask { Id = i }; stack.Push(workitem); Console.WriteLine(string.Format("把{0} 元素添加到ConcurrentStack",workitem.Id)); } } static async Task TaskProcess(ConcurrentStack<CustomTask> stack,string name,CancellationToken token) { CustomTask workitem; bool popSuccesful = false; await GetRandomDely(); do { popSuccesful = stack.TryPop(out workitem); if (popSuccesful) { Console.WriteLine(string.Format("元素 {0} ConcurrentStack 取出 ,名称:{1} ", workitem.Id, name)); } await GetRandomDely(); } while (!token.IsCancellationRequested); } static Task GetRandomDely() { int dely = new Random(DateTime.Now.Millisecond).Next(1, 1000); return Task.Delay(dely); } } }
2.程序的运行结果如下图。
当程序运行时,我们创建了一个ConcurrentStack集合的实例。其余的代码与前一示例几乎一样,唯一不同之年是我们对并发堆栈使用了Push和TryPop方法,而对并发队列使用Enqueue和TryDequeue方法。
从上图结果中可以扯到任务处理的顺序被改变了。堆栈是一个LIFO集合,工作线程先处理最近的任务。在并发队列中,任务被处理的顺序与被添加的顺序几乎一样。这说明根据工作线程的数量,我们将在一定时间内处理先被创建的任务。而在堆栈中,早先创建的任务具有较低的优先级,而且直到生产者停止向堆栈中放入更多任务后,这个任务才有可能被处理。这行为是确定 的,最好在这种场景下使用队列。