队列是其元素以先进先出(FirstIn,FirstOut,FIFO)的方式来处理的集合。先放入队列中的元素会先读取。队列的例子有在机场排的队列、人力资源部中等待处理求职信的队列和打印队列中等待处理的打印任务,以及按循环方式等待CPU处理的线程。另外,还常常有元素根据其优先级来处理的队列。
注:
也可使用链表的另一种实现方式来定义优先级列表。
队列使用System.Collections.Generic名称空间中的泛型类Queue<T>实现。在内部,Queue<T>类使用T类型的数组,这类似与List<T>类型。它实现ICollection和IEnumerable<T>接口,但没有实现ICollection<T>接口,因为这个接口定义了Add()和Remove()方法不能用于队列。
因为Queue<T>类没有实现IList<T>接口,所以不能用索引器访问队列。队列只允许在队列中添加元素,该元素会放在队列的尾部(使用Enqueue()方法),从队列的头部获取元素(使用Dequeue()方法)。
Enqueue()方法在队列的一端添加元素,Dequeue()方法在队列的另一端读取和删除元素。再次调用Dequeue()方法,会删除队列的下一项。
Queue<T>类的方法如下表所示:
在创建队列时,可以使用与List<T>类型类似的构造函数。虽然默认的构造函数会创建一个空队列,但也可以使用构造函数指定容量。在把元素添加到队列时,如果没有定义容量,容量就会递增,从而包含4,8,16和32个元素。类似于List<T>类,队列的容量也总是根据需要成倍增加。非泛型类Queue的默认构造函数与此不同,它会创建一个包含32项空的初始数组。使用构造函数的重载版本,还可以将实现了IEnumerable<T>接口的其他集合复制到队列中。
下面的文档管理应用程序示例说明了Queue<T>类的用法。使用一个线程将文档添加到队列中,用另一个线程从队列中读取文档,并处理它们。
存储在队列中的项是Document类型。Document类定义了标题和内容:
public class Document{
public string Title{get;}
public string Content{get;}
public Document(string title,string content){
Title = title;
Content = content;
}
}
DocumentManger类是Queue<T>类外面的一层。DocumentManage类定义了如何处理文档:
用AddDocument()方法将文档添加到队列中,用GetDocument()方法从队列中获得文档。
在AddDocument()方法中,用Enqueue()方法把文档添加到队列的尾部。在GetDocument()方法中,用Dequeue()方法从队列中读取第一个文档。因为多个线程可以同时访问DocumentManager类,所以用lock语句锁定对队列的访问。
IsDocumentAvailable是一个只读类型的布尔属性,如果队列中还有文档,就返回true,否则返回false:
using System;
using System.Collections.Generic;
namespace 队列Queue
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
public class Document{
public string Title{get;}
public string Content{get;}
public Document(string title,string content){
Title = title;
Content = content;
}
}
public class DocumentManager{
private readonly object _syncQueue = new object();
private readonly Queue<Document> _documentQueue = new Queue<Document>();
public void AddDocumenet(Document doc){
lock(_syncQueue){
_documentQueue.Enqueue(doc);
}
}
public Document GetDocument(){
Document doc = null;
lock(_syncQueue){
doc = _documentQueue.Dequeue();
}
return doc;
}
public bool IsDocumentAvailable=>_documentQueue.Count > 0;
}
}
ProcessDocuments类在一个单独的任务中处理队列中的文档。能从外部访问的唯一方法是Start()。在Start()方法中,实例化了一个新任务。创建一个ProcessDocuments对象,来启动任务,定义Run()方法作为任务的启动方法。TaskFactory(通过Task类的静态属性Factory访问)的StartNew方法需要一个Action委托作为参数,用于接受Run方法传递的地址。TaskFactory的StartNew方法会立即启动任务。
使用ProcessDocuments类的Run()方法定义了一个无限循环。在这个循环中,使用属性IsDocumentAvailable确定队列中是否还有文档。如果队列中还有文档,就从DocumentManager类中提取文档并处理。这里的处理仅是把信息写入控制台。在真正的应用程序中,文档还可以写入文件、数据库、或通过网络发送:
public class ProcessDocuments{
public static Task Start(DocumentManager dm)=>
Task.Run(new ProcessDocuments(dm).Run);
protected ProcessDocuments(DocumentManager dm)=>
_documentManager = dm ?? throw new ArgumentException(nameof(dm));
private DocumentManager _documentManager;
protected async Task Run(){
while(true){
if(_documentManager.IsDocumentAvailable){
Document doc = _documentManager.GetDocument();
System.Console.WriteLine($"Processing document {doc.Title}");
}
}
await Task.Delay(new Random().Next(20));
}
}
在应用程序的Main()方法中,实例化一个DocumentManager对象,启动文档处理任务。接着创建1000个文档。并添加到DocumentManager对象中:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace 队列Queue
{
class Program
{
static async Task Main(string[] args)
{
var dm = new DocumentManager();
Task processDocuments = ProcessDocuments.Start(dm);
//Creat documents and add them to the DocumentManager
for(int i = 0; i < 100; i++)
{
var doc = new Document($"Doc {i.ToString()}","content");
dm.AddDocumenet(doc);
System.Console.WriteLine($"Added document {doc.Title}");
await Task.Delay(new Random().Next(20));
}
await processDocuments;
//Console.WriteLine("Hello World!");
}
}
public class Document{
public string Title{get;}
public string Content{get;}
public Document(string title,string content){
Title = title;
Content = content;
}
}
public class DocumentManager{
private readonly object _syncQueue = new object();
private readonly Queue<Document> _documentQueue = new Queue<Document>();
public void AddDocumenet(Document doc){
lock(_syncQueue){
_documentQueue.Enqueue(doc);
}
}
public Document GetDocument(){
Document doc = null;
lock(_syncQueue){
doc = _documentQueue.Dequeue();
}
return doc;
}
public bool IsDocumentAvailable=>_documentQueue.Count > 0;
}
public class ProcessDocuments{
public static Task Start(DocumentManager dm)=>
Task.Run(new ProcessDocuments(dm).Run);
protected ProcessDocuments(DocumentManager dm)=>
_documentManager = dm ?? throw new ArgumentException(nameof(dm));
private DocumentManager _documentManager;
protected async Task Run(){
while(true){
if(_documentManager.IsDocumentAvailable){
Document doc = _documentManager.GetDocument();
System.Console.WriteLine($"Processing document {doc.Title}");
}
await Task.Delay(new Random().Next(20));
}
}
}
}
启动应用程序,会在队列中添加和删除文档。
完成示例应用程序描述的任务的真实程序可以处理用Web服务接受收的文档。