.Net中的并行编程-4.实现高性能异步队列
上文《.Net中的并行编程-3.ConcurrentQueue实现与分析》分析了ConcurrentQueue的实现,本章就基于ConcurrentQueue实现一个高性能的异步队列,该队列主要用于实时数据流的处理并简化多线程编程模型。设计该队列时考虑以下几点需求(需求来自公司的一个实际项目):
1. 支持多线程入队出队,尽量简化多线程编程的复杂度。
2. 支持事件触发机制,数据入队时才进行处理而不是使用定时处理机制, 而且内部能阻塞消费者线程。
3. 出队时数据处理的顺序要保证和入队时是一致的。
4. 容错性强,可以不间断运行。
以上需求点对应的解决方案:
1.ConcurrentQueue支持多线程而且多线程环境下的性能较高,对于多线程编程模型简化可用适配器模式可将消费者线程封装到队列内部,内部采用处理事件方式处理用户的任务。
2.对于事件触发机制首先信号量不适合,因为信号量达到指定数目时会阻塞线程,所以该部分需要自己编程实现(具体参考源码)。
3.队列的特性以及保证入队和出队顺序,这里需要保证的是线程处理数据项的顺序。
4.可通过注册异常处理函数的方式解决异常的问题。
所以开发出以下代码:
public class AsynQueue<T>
{
//队列是否正在处理数据
private int isProcessing;
//有线程正在处理数据
private const int Processing = ;
//没有线程处理数据
private const int UnProcessing = ;
//队列是否可用
private volatile bool enabled = true;
private Task currentTask;
public event Action<T> ProcessItemFunction;
public event EventHandler<EventArgs<Exception>> ProcessException;
private ConcurrentQueue<T> queue; public AsynQueue()
{
queue = new ConcurrentQueue<T>();
Start();
} public int Count
{
get
{
return queue.Count;
}
} private void Start()
{
Thread process_Thread = new Thread(PorcessItem);
process_Thread.IsBackground = true;
process_Thread.Start();
} public void Enqueue(T items)
{
if (items == null)
{
throw new ArgumentException("items");
} queue.Enqueue(items);
DataAdded();
} //数据添加完成后通知消费者线程处理
private void DataAdded()
{
if (enabled)
{
if (!IsProcessingItem())
{
currentTask = Task.Factory.StartNew(ProcessItemLoop);
}
}
} //判断是否队列有线程正在处理
private bool IsProcessingItem()
{
return !(Interlocked.CompareExchange(ref isProcessing, Processing, UnProcessing) == );
} private void ProcessItemLoop()
{ if (!enabled && queue.IsEmpty)
{
Interlocked.Exchange(ref isProcessing, );
return;
}
//处理的线程数 是否小于当前最大任务数
//if (Thread.VolatileRead(ref runingCore) <= this.MaxTaskCount)
//{
T publishFrame; if (queue.TryDequeue(out publishFrame))
{ try
{
ProcessItemFunction(publishFrame);
}
catch (Exception ex)
{
OnProcessException(ex);
}
} if (enabled && !queue.IsEmpty)
{
currentTask = Task.Factory.StartNew(ProcessItemLoop);
}
else
{
Interlocked.Exchange(ref isProcessing, UnProcessing);
}
} /// <summary>
///定时处理线程调用函数
///主要是监视入队的时候线程 没有来的及处理的情况
/// </summary>
private void PorcessItem(object state)
{
int sleepCount = ;
int sleepTime = ;
while (enabled)
{
//如果队列为空则根据循环的次数确定睡眠的时间
if (queue.IsEmpty)
{
if (sleepCount == )
{
sleepTime = ;
}
else if (sleepCount <= )
{
sleepTime = * ;
}
else
{
sleepTime = * ;
}
sleepCount++;
Thread.Sleep(sleepTime);
}
else
{
//判断是否队列有线程正在处理
if (enabled && Interlocked.CompareExchange(ref isProcessing, Processing, UnProcessing) == )
{
if (!queue.IsEmpty)
{
currentTask = Task.Factory.StartNew(ProcessItemLoop);
}
else
{
Interlocked.Exchange(ref isProcessing, );
}
sleepCount = ;
sleepTime = ;
}
}
}
} public void Flsuh()
{
Stop(); if (currentTask != null)
{
currentTask.Wait();
} while (!queue.IsEmpty)
{
try
{
T publishFrame;
if (queue.TryDequeue(out publishFrame))
{
ProcessItemFunction(publishFrame);
}
}
catch (Exception ex)
{
OnProcessException(ex);
}
}
currentTask = null;
} public void Stop()
{
this.enabled = false;
} private void OnProcessException(System.Exception ex)
{
var tempException = ProcessException;
Interlocked.CompareExchange(ref ProcessException, null, null); if (tempException != null)
{
ProcessException(ex, new EventArgs<Exception>(ex));
}
}
}
[Serializable]
public class EventArgs<T> : System.EventArgs
{
public T Argument; public EventArgs() : this(default(T))
{
} public EventArgs(T argument)
{
Argument = argument;
}
}
该队列的思想是:当每次数据入队时,队列内部会调用DataAdded()判断是否数据项已经开始被处理,如果已经开始处理则数据入到内部队列后直接返回否则开启消费者线程处理。队列内部的消费者线程(线程池)(Task内部使用线程池实现,这里就当做线程池吧)会采用采用递归的方式处理数据,也就是当前数据处理完成后再将另外一个数据放到线程池去处理,这样就形成一个处理环而且保证了每条数据都有序的进行处理。由于ConcurrentQueue的IsEmpty只是当前内存的一个快照状态,可能当前时刻为空下一个时候不为空, 所以还需要一个守护线程process_Thread定时监视队列内部的消费者线程(线程池)是否正在处理数据,否则会造成消费者线程已经判断队列为空而数据已经到达只是还没插入队列此时数据可能永远得不到处理。
适用的场景:
1.适合多个生产者一个消费者的情景(当前如果需要多个消费者可以使用多个单独线程来实现)。
2.适合处理数据速度较快的情景而对于文件写入等IO操作不适合,因为线程池内部都是后台线程,当进程关闭时线程会同时关闭线程这时文件可能还没写入到磁盘。
3.适合作为流水线处理模型的基础数据结构,队列之间通过各自的事件处理函数进行通信(后续会专门撰写文章介绍关于流水线模型的应用)。
注:内部的ConcurrentQueue队列还可以使用阻塞队列(BlockingCollection)来替代,虽然使用阻塞队列更简单但是内部的消费者线程比较适合使用单独的线程不适合使用线程池,而且阻塞队列为空时会阻塞消费者线程,当然阻塞线程池内的线程也没什么影响只是不推荐这么做,而且阻塞的队列的性能也没有ConcurrentQueue的性能高。
.Net中的并行编程-4.实现高性能异步队列的更多相关文章
- .Net中的并行编程-6.常用优化策略
本文是.Net中的并行编程第六篇,今天就介绍一些我在实际项目中的一些常用优化策略. 一.避免线程之间共享数据 避免线程之间共享数据主要是因为锁的问题,无论什么粒度的锁 ...
- .Net中的并行编程-5.流水线模型实战
自己在Excel整理了很多想写的话题,但苦于最近比较忙(其实这是借口).... 上篇文章<.Net中的并行编程-4.实现高性能异步队列>介绍了异步队列的实现,本篇文章介绍我实际工作者遇到了 ...
- .Net中的并行编程-1.路线图(转)
大神,大神,膜拜膜拜,原文地址:http://www.cnblogs.com/zw369/p/3834559.html 目录 .Net中的并行编程-1.路线图 分析.Net里线程同步机制 .Net中的 ...
- .Net中的并行编程-2.ConcurrentStack的实现与分析
在上篇文章<.net中的并行编程-1.基础知识>中列出了在.net进行多核或并行编程中需要的基础知识,今天就来分析在基础知识树中一个比较简单常用的并发数据结构--.net类库中无锁栈的实现 ...
- .Net中的并行编程-3.ConcurrentQueue实现与分析
在上文<.Net中的并行编程-2.ConcurrentQueue的实现与分析> 中解释了无锁的相关概念,无独有偶BCL提供的ConcurrentQueue也是基于原子操作实现, 由于Con ...
- Python中的并行编程速度
这里主要想记录下今天碰到的一个小知识点:Python中的并行编程速率如何? 我想把AutoTool做一个并行化改造,主要目的当然是想提高多任务的执行速度.第一反应就是想到用多线程执行不同模块任务,但是 ...
- .Net中的并行编程-7.基于BlockingCollection实现高性能异步队列
三年前写过基于ConcurrentQueue的异步队列,今天在整理代码的时候发现当时另外一种实现方式-使用BlockingCollection实现,这种方式目前依然在实际项目中使用.关于Blockin ...
- .Net中的并行编程-1.路线图
最近半年一直研究用.net进行并行程序的开发与设计,再研究的过程中颇有收获,所以画了一个图总结了一下并行编程的基础知识点,这些知识点是并行编程的基础,有助于我们编程高性能的程序,里面的某些结构实现机制 ...
- Python并行编程(十四):异步编程
1.基本概念 除了顺序执行和并行执行的模型以外,还有异步模型,这是事件驱动模型的基础.异步活动的执行模型可以只有一个单一的主控制流,能在单核心系统和多核心系统中运行. 在并发执行的异步模型中,许多任务 ...
随机推荐
- hibernate(四) 双向多对多映射关系
序言 莫名长了几颗痘,真TM疼,可能是现在运动太少了,天天对着电脑,决定了,今天下午花两小时去跑步了, 现在继上一章节的一对多的映射关系讲解后,今天来讲讲多对多的映射关系把,明白了一对多,多对多个人感 ...
- iReport 中使用 Chart 图
iReport 中使用 Chart 图 SSH2项目中需要引入如下两个jar包: jfreechart-1.0.12.jar jcommon-1.0.15.jar 从 iReport 的安装目录下搜索 ...
- 行集函数:OpenRowSet 和 OpenQuery
在SQL Server中,行集函数是不确定性的,这意味着,每次调用,返回值不总是相同的.返回值是不确定的,这意味着,对于相同的输入值,不保证每次返回的值都是相同的.对行集函数的每次调用,行集函数都是单 ...
- (4) PIVOT 和 UPIVOT 的使用
最近项目中用到了行转列,使用SQL SERVER 提供的PIVOT实现起来非常容易. 官方解释:详见这里 可以使用 PIVOT 和 UNPIVOT 关系运算符将表值表达式更改为另一个表. PIVOT ...
- CSS光标cursor
前面的话 在浏览器中,光标对于提供交互反馈很有用.通过在不同的场景中改变光标,就能赋予其不同的含义 定义 cursor光标 值: [<uri>,]*[auto | default | po ...
- linux安装nginx
nginx启动.重启.关闭 安装: http://www.cnblogs.com/skynet/p/4146083.html 一.启动 cd usr/local/nginx/sbin ./nginx ...
- JavaScript获取图片的原始尺寸
页面里的img元素,想要获取它的原始尺寸,以宽度为例可能首先想到的就是width,如下 <img src="http://img11.360buyimg.com/da/g14/M07/ ...
- double保持精度,防止小数点后数字的丢失的小方法
一般情况下,输入带小数点的字面值,编译器会把它解析成double 类型. 例如:一个字面值被直接放到代码中,由于带小数点所以,默认值为double类型 输出结果是:1.12345678912345 ...
- 转载-centos网络配置(手动设置,自动获取)的2种方法
转载地址:http://blog.51yip.com/linux/1120.html 重新启动网络配置 # service network restart 或 # /etc/init.d/networ ...
- javascript学习6
JavaScript Boolean(逻辑)对象 Boolean(逻辑)对象用于将非逻辑值转换为逻辑值(true 或者 false). 实例 检查逻辑值 检查逻辑对象是 true 还是 false. ...