队列
前言:队列是其元素以先进先出的方式来处理的集合。先放入队列中的元素会先读取。队列的例子有在机场排的队列、人力资源部中等待处理求职信的队列和打印队列中等待处理的打印任务。另外,还常常有元素根据其优先级来处理队列。例如:在机场的队列中,商务舱乘客的处理要优于经济舱的乘客。这里可以使用多个队列,一个队列对应一个优先级。我们可以为一组队列建立一个数组,数组中的一项代表一个优先级。在每个数组项中都有一个队列,其中也是按照先进先出的方式进行处理。
队列使用System.Collections.Generic命名空间中的泛型类Queue<T>实现。在内部,Queue<T>类使用T类型的数组。这就类似于List<T>类型。它实现ICollection和IEnumerable<T>接口。但是没有实现ICollection<T>接口。因为这个接口定义的Add()和Remove()方法不能用于队列。
下图显示的队列的元素。Enqueue()方法在队列的一段添加元素。Dequeue()方法在队列的另一端读取和删除元素。再次调用Dequeue()方法,会删除队列中的下一项。
下图表示Queue<T>类的方法:
在创建队列的时候,可以使用与List<T>类型类似的构造函数。虽然默认的构造函数会创建一个空队列。但也可以使用构造函数指定其容量。在把元素添加到队列中。如果没有定义其容量,容就会递增。从而包含4、8、16、32个元素,类似于List<T>类,队列的容量也总是根据需要成倍的增加。非泛型类Queue的默认构造函数于此不同。它会创建一个包含32项的空数组。使用构造函数的重载版本。还可以将实现了IEnumberable<T>接口的其他集合复制到队列中。
存储在队列中的项是Document类型,类中定义了标题和内容:
/// <summary> /// 文档类 /// </summary> public class Document { public string Title { get; internal set; } //标题 public string Content { get; internal set; } //内容 /// <summary> /// 构造函数初始化赋值 /// </summary> /// <param name="title"></param> /// <param name="content"></param> public Document(string title, string content) { this.Title = title; this.Content = content; } }
DocumentManager类是Queue<T>是类外面的一层。DocumentManager类定义了如何处理文档:用AddDocument()方法将文档添加到队列中,用GetDocument()方法从队列中获得文档。
在AddDocument()方法中,用Enqueue()方法把文档添加到队列的尾部。在GetDocument()方法中,用Dequeue()方法从队列中读取第一个文档。对个线程可以同时访问DocumentManager类,所以用Lock(在别的博客中详细的讲解)语句锁定对队列的访问。
IsDocumentAvailable是一个只读类型的布尔属性,如果队列中还有文档。它返回true,否则返回false。代码如下:
/// <summary> /// 文档的管理器 /// </summary> public class DocumentManager { private readonly Queue<Document> _documents = new Queue<Document>(); /// <summary> /// 添加文档 /// </summary> /// <param name="doc"></param> public void AddDocument(Document doc) { lock (this) { _documents.Enqueue(doc); } } /// <summary> /// 移除并且返回对象 /// </summary> /// <returns></returns> public Document GetDocument() { Document doc = null; lock (this) { doc = _documents.Dequeue(); } return doc; } /// <summary> /// 只读类型的布尔属性 /// </summary> public bool IsDocumentAvailable { get { return _documents.Count > 0; } } }
ProcessDocuments类在一个单独的任务中处理队列中的文档。能从外部访问的唯一方法是Start()。在Start()方法中,实例化一个新任务。创建一个ProcessDocuments对象。来启动任务。定义Run()方法作为任务的启动方法。TaskFactory(通过Task类的静态属性Factory访问)的StartNew方法需要一个Action委托作为参数,用于接受Run()方法传递的地址。TaskFactory的StartNew方法会立即启动任务。
使用ProcessDocuments类的Run()方法定义一个无限循环。在这个循环中,使用属性IsDocumentAvailable确定队列中是否还有文档。如果队列中还有文档,就从DocumentManager类中提取文档并处理。这个的处理是把信息写入控制台(在真正的应用程序中,文档可以写入文件、数据库)。
/// <summary> /// 文档的处理类 /// </summary> public class ProcessDocuments { private readonly DocumentManager _documentManager; /// <summary> /// 在我们开始的方法中实例化一个新任务 /// </summary> /// <param name="document"></param> public static void Start(DocumentManager document) { //异步操作创建并且开启任务 Task.Factory.StartNew(() => new ProcessDocuments(document).Run()); } /// <summary> /// 构造函数 /// </summary> /// <param name="dm"></param> protected ProcessDocuments(DocumentManager dm) { if (dm == null) throw new ArgumentNullException(nameof(dm)); _documentManager = dm; } /// <summary> /// 无限循环的方法 /// </summary> protected void Run() { while (true) { //如果队列中还有文档 if (_documentManager.IsDocumentAvailable) { //然后提取文档 Document doc = _documentManager.GetDocument(); //在控制台中进行结果的打印 Console.WriteLine("处理并且移除的文件:{0}", doc.Title); } //当当前的线程挂起指定的时间 Thread.Sleep(new Random().Next(20)); } } }
在应用程序的Main方法中,实例化一个文档管理器DocumentManager对象,启动文档处理任务。接着创建50个文档并且添加到DocumentManager对象中。
static void Main(string[] args) { //创建一个文档管理器 var dm = new DocumentManager(); //开始处理任务 ProcessDocuments.Start(dm); //循环的添加文档 for (int i = 0; i < 50; i++) { var doc = new Document(i.ToString(), "content"); //添加文档到队列中 dm.AddDocument(doc); Console.WriteLine("添加文档 :{0}", doc.Title); Thread.Sleep(new Random().Next(20)); } Console.ReadKey(); }
打印的结果如下:
如有瑕疵请不吝指教,希望大家多多留言!!