我有以下用例。多个线程正在创建在ConcurrentBag中收集的数据点。单个消费者线程每隔x ms查看自上次以来进入的数据点并对其进行处理(例如计算它们+计算平均值)。
以下代码或多或少代表了我提出的解决方案:
private static ConcurrentBag<long> _bag = new ConcurrentBag<long>();
static void Main()
{
Task.Run(() => Consume());
var producerTasks = Enumerable.Range(0, 8).Select(i => Task.Run(() => Produce()));
Task.WaitAll(producerTasks.ToArray());
}
private static void Produce()
{
for (int i = 0; i < 100000000; i++)
{
_bag.Add(i);
}
}
private static void Consume()
{
while (true)
{
var oldBag = _bag;
_bag = new ConcurrentBag<long>();
var average = oldBag.DefaultIfEmpty().Average();
var count = oldBag.Count;
Console.WriteLine($"Avg = {average}, Count = {count}");
// Wait x ms
}
}
修改
我猜上面的代码并不能很好地反映出我想要实现的目标。所以这里有一些代码来显示问题:
public class LogCollectorTarget : TargetWithLayout, ILogCollector
{
private readonly List<string> _logMessageBuffer;
public LogCollectorTarget()
{
_logMessageBuffer = new List<string>();
}
protected override void Write(LogEventInfo logEvent)
{
var logMessage = Layout.Render(logEvent);
lock (_logMessageBuffer)
{
_logMessageBuffer.Add(logMessage);
}
}
public string GetBuffer()
{
lock (_logMessageBuffer)
{
var messages = string.Join(Environment.NewLine, _logMessageBuffer);
_logMessageBuffer.Clear();
return messages;
}
}
}
班级&#39;目的是收集日志,以便将它们分批发送到服务器。每隔x秒调用一次GetBuffer。这应该获取当前日志消息并清除新消息的缓冲区。它适用于锁,但因为它们非常昂贵,我不想锁定程序中的每个Logging操作。这就是我想将ConcurrentBag用作缓冲区的原因。但是当我调用GetBuffer而不丢失切换期间发生的任何日志消息时,我仍然需要切换或清除它。
答案 0 :(得分:1)
由于您只有一个消费者,因此您可以使用简单的ConcurrentQueue,而无需交换集合:
printInformation(employees["joe"])
如果内存分配成为问题,您可以将它们出列到固定大小的数组并在其上调用public class LogCollectorTarget : TargetWithLayout, ILogCollector
{
private readonly ConcurrentQueue<string> _logMessageBuffer;
public LogCollectorTarget()
{
_logMessageBuffer = new ConcurrentQueue<string>();
}
protected override void Write(LogEventInfo logEvent)
{
var logMessage = Layout.Render(logEvent);
_logMessageBuffer.Enqueue(logMessage);
}
public string GetBuffer()
{
// How many messages should we dequeue?
var count = _logMessageBuffer.Count;
var messages = new StringBuilder();
while (count > 0 && _logMessageBuffer.TryDequeue(out var message))
{
messages.AppendLine(message);
count--;
}
return messages.ToString();
}
}
。这样,您可以保证只进行两次分配(如果初始缓冲区的大小不合适,StringBuilder可以执行更多操作):
string.Join
答案 1 :(得分:0)
ConcurrentBag是否适合这项工作?
它是适合工作的正确工具,这实际上取决于你想要做什么,以及为什么。你给出的例子非常简单,没有任何背景,所以很难说。
正在切换行李箱以清除列表的正确方法 新数据点然后处理旧数据?
答案是否定的,可能有很多原因。如果一个线程在切换它时会发生什么?
对oldBag进行操作是否安全,或者当我遇到麻烦时可能会遇到麻烦 迭代oldBag并且线程仍在添加项目?
不,你刚刚复制了参考资料,这将无所作为。
我应该使用Interlocked.Exchange()来切换变量吗?
互锁方法很棒,但是这对你当前的问题没有帮助,它们用于线程安全访问整数类型值。你真的很困惑,你需要查找更多线程安全的例子。
但是让我们指出你正确的方向。忘记ConcurrentBag和那些花哨的课程。我的建议是从简单开始并使用锁定,以便您了解问题的本质。
如果您希望多个任务/线程访问列表,您可以轻松使用lock
语句并保护对列表/数组的访问权限,以便其他讨厌的线程不会对其进行修改。
显然你编写的代码是一个荒谬的例子,我的意思是你只是在列表中添加连续的数字,并获得另一个线程来对它们进行平均。这根本不需要是消费者生产者,而且只是同步更有意义。
此时我会指出更好的架构,可以让你实现这种模式,例如Tpl Dataflow,但我担心这只是一个学习消费者,不幸的是你真的需要做更多关于多线程的阅读并尝试更多的例子在我们真正帮助您解决问题之前。