这是种模式在现实生活中的例子很多:
邮局寄信
生产者:你,消费者:投递员,任务列表:邮筒
你写信然后扔到邮筒中去,给任务列表中添加了一个任务。投递员取走有邮筒里的信,消费掉任务列表里的一个任务。
邮局这样做的好处在于:
1.解耦 你不必去认识投递员,万一认识的那个投递员不干了,你又要重新认识一个投递员。
2.支持并发 你不必在某个地点傻等着投递员,同时,投递员也不需挨家挨户的问,哪家需要寄信。
对比邮局寄信的事情,类似博客、论坛等发文章的网站,创建文章索引库也有些类似。
生产者:创建任务,添加到任务列表中,例如添加一篇随笔。
消费者:将任务列表中,某一篇随笔添加到索引库中,这样在搜索的时候,才能够搜索出来新发的随笔。
创建索引库是耗时很长的工作,所以启动有一个消费者线程一直保持对IndexWriter写的状态,有新任务进入的时候对IndexWriter写入,写入完成之后关闭。然后下次while循环扫描的时候判断如果队列汇总没有任务,则sleep5秒钟后再判断,防止不断判断给服务器cpu压力。
IndexManager.cs代码:
public class IndexManager
{
//单例
private IndexManager()
{
}
private static IndexManager instance = new IndexManager();
public static IndexManager Instance()
{
return instance;
}
//任务列表
private List<IndexJob> jobs = new List<IndexJob>();
//启动消费者线程
public void Start()
{
Thread threadIndex = new Thread(Index);
threadIndex.IsBackground = true;
threadIndex.Start();
}
//创建索引
private void Index()
{
while (true)
{
//防止空转造成cpu占用率过高
if (jobs.Count <= 0)
{
//logger.Debug("没有任务,再睡会!");
Thread.Sleep(5 * 1000);
continue;
}
//为什么每次循环都要打开、关闭索引库。因为关闭索引库以后才会把写入的数据提交到索引库中。也可以每次操作都“提交”(参考Lucene.net文档)
string indexPath = "c:/cmsindex";
FSDirectory directory = FSDirectory.Open(new DirectoryInfo(indexPath), new NativeFSLockFactory());
bool isUpdate = IndexReader.IndexExists(directory);
//logger.Debug("索引库存在状态" + isUpdate);
if (isUpdate)
{
//如果索引目录被锁定(比如索引过程中程序异常退出),则首先解锁
if (IndexWriter.IsLocked(directory))
{
//logger.Debug("开始解锁索引库");
IndexWriter.Unlock(directory);
//logger.Debug("解锁索引库完成");
}
}
IndexWriter writer = new IndexWriter(directory, new PanGuAnalyzer(), !isUpdate, Lucene.Net.Index.IndexWriter.MaxFieldLength.UNLIMITED);
//后台线程的实际工作
ProcessJobs(writer);
writer.Close();
directory.Close();//不要忘了Close,否则索引结果搜不到
//logger.Debug("全部索引完毕");
}
}
//后台线程工作
private void ProcessJobs(IndexWriter writer)
{
foreach (var job in jobs.ToArray())
{
//todo:异常处理
jobs.Remove(job);// 消费掉
//因为是自己的网站,所以直接读取数据库,不用webclient了
//为避免重复索引,所以先删除number=i的记录,再重新添加
writer.DeleteDocuments(new Term("number", job.Id.ToString()));
//如果“添加文章”任务再添加,
if (job.JobType == JobType.Add)
{
BLL.newsrupeng newBll = new BLL.newsrupeng();
var art = newBll.GetModel(job.Id);
if (art == null)//有可能刚添加就被删除了
{
continue;
}
string title = art.title;
string body = art.content;//去掉标签
Document document = new Document();
//只有对需要全文检索的字段才ANALYZED
document.Add(new Field("number", job.Id.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
document.Add(new Field("title", title, Field.Store.YES, Field.Index.NOT_ANALYZED));
document.Add(new Field("body", body, Field.Store.YES, Field.Index.ANALYZED, Lucene.Net.Documents.Field.TermVector.WITH_POSITIONS_OFFSETS));
writer.AddDocument(document);
//logger.Debug("索引" + job.Id + "完毕");
}
}
}
//添加任务
public void AddArticle(int artId)
{
IndexJob job = new IndexJob();
job.Id = artId;
job.JobType = JobType.Add;
//logger.Debug(artId+"加入任务列表");
jobs.Add(job);//把任务加入商品库
}
//删除任务
public void RemoveArticle(int artId)
{
IndexJob job = new IndexJob();
job.JobType = JobType.Remove;
job.Id = artId;
//logger.Debug(artId + "加入删除任务列表");
jobs.Add(job);//把任务加入商品库
}
}
/// <summary>
/// 索引任务
/// </summary>
class IndexJob
{
public int Id { get; set; }
public JobType JobType { get; set; }
}
enum JobType { Add, Remove }
一个winform例子更佳能够体现这个模式。
启动后台线程,向文本框中输入值,然后生产,listbox中经过5秒之后,才能显示出来刚刚添加文本。
代码:
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}
private List<string> jobList = new List<string>();
//添加任务列表
private void button1_Click(object sender, EventArgs e)
{
if (textBox1.Text!="")
{
jobList.Add(textBox1.Text);
textBox1.Clear();
textBox1.Focus();
}
}
private void button2_Click(object sender, EventArgs e)
{
Start();
}
private void Start()
{
Thread thread = new Thread(ProcessDo);
thread.IsBackground = true;
thread.Start();
}
//工作
private void ProcessDo()
{
while (true)
{
if (jobList.Count<=0)
{
Thread.Sleep(5*1000);
continue;
}
foreach (var s in jobList.ToArray())
{
MyDelegate d = (txt) =>
{
listBox1.Items.Add(s);
};
listBox1.Invoke(d, s);
jobList.Remove(s);//消费了商品就把商品从“仓库”中移除掉
}
}
}
}
delegate void MyDelegate(string s);