C# 批处理ConcurrentBag中的所有项目

C# 批处理ConcurrentBag中的所有项目,c#,concurrency,C#,Concurrency,我有以下用例。多个线程正在创建在ConcurrentBag中收集的数据点。每x毫秒,一个使用者线程会查看自上次以来传入的数据点并对其进行处理(例如,计数+计算平均值) 以下代码或多或少代表了我提出的解决方案: private static ConcurrentBag<long> _bag = new ConcurrentBag<long>(); static void Main() { Task.Run(() => Consume()); var

我有以下用例。多个线程正在创建在ConcurrentBag中收集的数据点。每x毫秒,一个使用者线程会查看自上次以来传入的数据点并对其进行处理(例如,计数+计算平均值)

以下代码或多或少代表了我提出的解决方案:

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
    }
}
private static ConcurrentBag\u bag=new ConcurrentBag();
静态void Main()
{
Task.Run(()=>Consume());
var producerTasks=Enumerable.Range(0,8)。选择(i=>Task.Run(()=>product());
Task.WaitAll(producertask.ToArray());
}
私有静态void product()
{
对于(int i=0;i<100000000;i++)
{
_添加(i);
}
}
私有静态void消费()
{
while(true)
{
var oldBag=_bag;
_bag=新的ConcurrentBag();
var average=oldBag.DefaultIfEmpty().average();
var count=oldBag.count;
WriteLine($“Avg={average},Count={Count}”);
//等待x毫秒
}
}
  • ConcurrentBag是适合这项工作的工具吗
  • 交换行李是否是实现清除新数据点列表,然后处理旧数据点的正确方法
  • 在oldBag上操作安全吗?或者当我在oldBag上迭代时,线程仍在添加项目,我会遇到麻烦吗
  • 我应该使用Interlocked.Exchange()来切换变量吗
编辑

我想上面的代码并不能很好地表达我想要实现的目标。下面是一些代码来说明问题:

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;
        }
    }
}
公共类日志采集器目标:带有布局的目标,ILogCollector
{
私有只读列表_logMessageBuffer;
公共日志收集器目标()
{
_logMessageBuffer=新列表();
}
受保护的重写无效写入(LogEventInfo logEvent)
{
var logMessage=Layout.Render(logEvent);
锁定(_logMessageBuffer)
{
_logMessageBuffer.Add(logMessage);
}
}
公共字符串GetBuffer()
{
锁定(_logMessageBuffer)
{
var messages=string.Join(Environment.NewLine,_logMessageBuffer);
_logMessageBuffer.Clear();
返回消息;
}
}
}
该类的用途是收集日志,以便可以将它们批量发送到服务器。每x秒调用一次GetBuffer。这将获取当前日志消息并清除新消息的缓冲区。它与锁一起工作,但由于它们非常昂贵,我不想锁定程序中的每个日志操作。这就是为什么我想使用ConcurrentBag作为缓冲区。但在调用GetBuffer时,我仍然需要切换或清除它,而不会丢失切换期间发生的任何日志消息

ConcurrentBag是适合这项工作的工具吗

这是一项工作的正确工具,这实际上取决于你想做什么,以及为什么。您给出的示例非常简单,没有任何上下文,因此很难说

交换行李是否是正确的方式,以实现清除行李清单 新数据点,然后处理旧数据点

答案是否定的,可能有很多原因。当您切换线程时,如果线程写入它会发生什么情况

在oldBag上操作安全吗?或者当我 在oldBag上迭代,线程仍在添加项目

不,您只是复制了引用,这将一事无成

我应该使用Interlocked.Exchange()来切换变量吗

互锁方法非常有用,但是这对解决当前的问题没有帮助,它们用于线程安全地访问整型值。您真的很困惑,需要查找更多线程安全示例


但是,让我们为您指出正确的方向。忘了ConcurrentBag和那些花哨的课程吧。我的建议是从简单开始并使用锁定,这样您就可以了解问题的本质

如果希望多个任务/线程访问列表,可以轻松使用
lock
语句保护对列表/数组的访问,这样其他讨厌的线程就不会修改它

显然,您编写的代码是一个毫无意义的示例,我的意思是,您只是将连续的数字添加到列表中,然后让另一个线程对它们进行平均。这几乎不需要是消费者生产者,而且如果是同步的,就更有意义了


在这一点上,我将向您介绍更好的体系结构,使您能够实现这种模式,例如Tpl数据流,但我担心这只是一种学习练习,不幸的是,在我们真正帮助您解决问题之前,您确实需要更多地阅读多线程并尝试更多的示例

由于您只有一个消费者,您可以使用简单的ConcurrentQueue,而无需交换集合:

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();
    }
}

谢谢你的评论。上面的例子不能很好地说明这个问题。我编辑了这个问题谢谢,我希望有一个“出列所有”的方法,但那应该行得通!
public string GetBuffer()
{
    // How many messages should we dequeue?
    var count = _logMessageBuffer.Count;
    var buffer = new string[count];

    for (int i = 0; i < count; i++)
    {   
        _logMessageBuffer.TryDequeue(out var message);
        buffer[i] = message;   
    }       

    return string.Join(Environment.NewLine, buffer);
}