Pur*_*tha 0 .net c# multithreading
我正在开发一个连接到x个硬件设备的应用程序.我设计了ReaderLayer,以便有一个专用线程,代码运行以连接到单个设备并连续读取设备数据缓冲区.阅读器层的代码如下:
while (true)
{
// read buffer from the reader
IList<IRampActiveTag> rampTagList = ConnectedReader.ReadBuffer();
if (rampTagList != null && rampTagList.Any())
{
// trigger read event handler
if (ReadMessage != null)
ReadMessage(this, new RampTagReadEventArg(rampTagList));
}
}
Run Code Online (Sandbox Code Playgroud)
在读取器层之上构建了一个逻辑层,负责处理从读取器接收的数据并通过HTTP Post转发它.它有多个线程,每个线程运行一个单独的逻辑块,必须处理写入其线程安全队列的相关信息.逻辑层订阅ReadForward由Reader Layer公开的事件,并通过写入它将该数据转发到相关的逻辑块ConcurrentQueue.
每个Logic Block中的代码非常简单:
public void ProcessLogicBuffer()
{
while (true)
{
// Dequeue the list
IRampActiveTag tag;
LogicBuffer.TryDequeue(out tag);
if (tag != null)
{
ProcessNewTagReceivedLogic(tag);
Console.WriteLine("Process Buffer #Tag {0}. Buffer Count #{1}", tag.NewLoopId, LogicBuffer.Count);
}
}
}
Run Code Online (Sandbox Code Playgroud)
读取器层和逻辑层的循环布局大致相同while(true).然而,当我测试3个读卡器和3个逻辑块时,我发现我的CPU利用率上升到77%.我很快将CPU使用率缩小到Logic Threads,因为我获得了2%的使用率和1个块的25%使用率.
只需在逻辑线程中添加3个块的Thread.Sleep(100),我就可以将CPU使用率降低到~3%,但是,我担心我的逻辑可能不同步.通过查看样本,任何人都可以向我建议任何改进,因为生产代码需要使用大约30个逻辑块.我需要改变我的架构吗?
你在这个循环中做了很多不必要的轮询:
public void ProcessLogicBuffer()
{
while (true)
{
// Dequeue the list
IRampActiveTag tag;
LogicBuffer.TryDequeue(out tag);
if (tag != null)
{
ProcessNewTagReceivedLogic(tag);
Console.WriteLine("Process Buffer #Tag {0}. Buffer Count #{1}", tag.NewLoopId, LogicBuffer.Count);
}
}
}
Run Code Online (Sandbox Code Playgroud)
假设大多数时间队列都是空的,这段代码所做的大部分工作都是重复检查队列."我有什么事吗?现在怎么样?现在?......"
您可以通过替换您摆脱所有无用的轮询ConcurrentQueue用BlockingCollection.假设您更改LogicBuffer为a BlockingCollection,则循环如下所示:
public void ProcessLogicBuffer()
{
foreach (var tag in LogicBuffer.GetConsumingEnumerable())
{
ProcessNewTagReceivedLogic(tag);
Console.WriteLine("Process Buffer #Tag {0}. Buffer Count #{1}", tag.NewLoopId, LogicBuffer.Count);
}
}
Run Code Online (Sandbox Code Playgroud)
GetConsumingEnumerable将在项目到达时将项目出列,并将继续这样做,直到该项目为空并且已标记为完成添加.请参见BlockingCollection.CompleteAdding.然而,真正的美丽GetConsumingEnumerable是非忙碌的等待.如果集合为空,则等待项目可用的通知.它没有做很多无用的轮询TryDequeue.
更改使用代码ConcurrentQueue,以便它使用BlockingCollection的却是很容易的.你可以在一小时内完成.这样做会使您的代码更简单,并且它将消除不必要的轮询.
如果您需要进行一些定期处理,您有两种选择.如果你想在读取的同一循环中完成BlockingCollection,那么你可以将使用的循环更改为GetConsumingEnumerable:
Stopwatch sw = Stopwatch.StartNew();
TimeSpan lastProcessTime = TimeSpan.Zero;
while (true)
{
IRampActiveTag tag;
// wait up to 200 ms to dequeue an item.
if (LogicBuffer.TryTake(out tag, 200))
{
// process here
}
// see if it's been 200 ms or more
if ((sw.ElapsedMilliseconds - lastProcessTime.TotalMilliseconds) > 200)
{
// do periodic processing
lastProcessTime = sw.Elapsed;
}
}
Run Code Online (Sandbox Code Playgroud)
这将为您提供200至400毫秒的定期处理速率.在我看来,它有点难看,而且对你的目的来说可能不够好.您可以将超时减少到20毫秒而不是200毫秒,这将使您更接近200毫秒的轮询速率,代价是10倍的呼叫TryTake.可能,您不会注意到CPU使用率的差异.
我倾向于将该周期性处理循环与队列使用者分开.创建一个每200毫秒触发一次的计时器,让它完成工作.例如:
public void ProcessLogicBuffer()
{
var timer = new System.Threading.Timer(MyTimerProc, null, 200, 200);
// queue processing stuff here
// Just to make sure that the timer isn't garbage collected. . .
GC.KeepAlive(timer);
}
private void MyTimerProc(object state)
{
// do periodic processing here
}
Run Code Online (Sandbox Code Playgroud)
这将使您的更新频率非常接近200毫秒.定时器proc将在一个单独的线程上执行,为true,但是线程处于活动状态的唯一时间是定时器触发时.