如何在多线程中对数据进行分组记录?

13 浏览
0 Comments

如何在多线程中对数据进行分组记录?

我有两个方法。一个方法包含线程池,线程池运行第二个方法。

以下是代码片段:

static void Main()
{
    var processes = GetProcessCounter();
    foreach (var process in processes)
    {
        var tmp = process;
        ThreadPool.QueueUserWorkItem(WriteCounter, tmp); 
        Console.Write("\n\n");
    }
    Console.WriteLine("按任意键退出...");
    Console.Read();
}
static void WriteCounter(object obj)
{
    var process = (ProcessInstance) obj;
    Console.WriteLine(
        $"线程ID: {Thread.CurrentThread.ManagedThreadId}, " +
        $"进程名称: {process.ProcessName}, " +
        $"进程ID: {process.ProcessId} ");
    Console.Write("\n");
    foreach (var cnt in process.Counters)
    {
        cnt.NextValue();
        Console.WriteLine(
            $"线程ID: {Thread.CurrentThread.ManagedThreadId} | " +
            $"分组: {cnt.CategoryName} | " +
            $"进程: {cnt.InstanceName} | " +
            $"名称: {cnt.CounterName} | " +
            $"数值: {cnt.GetCalculatedValue(cnt.CounterName)}");
        cnt.Close();
    }
}

我看到它的运行正常。当我计算子循环时,它会在一个线程中与父循环一起运行。但在控制台中,我看到混乱的输出。

我应该如何按线程ID分组输出?

0
0 Comments

在多线程中进行数据记录时,可能会遇到以下问题和解决方法:

问题:如何对数据记录进行分组?

原因:在多线程环境下,多个线程同时进行数据记录可能会导致输出的数据混乱或者丢失。

解决方法:

1. 使用Console.WriteLine时,每个线程只调用一次Console.WriteLineConsole.WriteLine是线程安全的,因此可以通过在线程中构建输出字符串,并在最后调用Console.WriteLine来解决该问题。

2. 在所有Console.WriteLine调用上显式使用一些线程同步机制,例如lock (consoleLock) { Console.WriteLine(...); for (...) Console.WriteLine(...)}。但是,像前面提到的一次构建整个输出字符串那样,可以一次性锁定,这是一个更好的方法。在使用锁时,应该锁定尽可能短的时间间隔,因为如果将整个线程启动委托放在锁中,与单线程解决方案相比,它不会快多少,而且由于很多线程池线程在等待共享资源,从性能角度来看可能是不明智甚至有害的。

3. 如果想要进行更高级的操作,可以尝试使用一些consumer-producer的实现(例如在控制台应用程序中同步不同线程的事件),其中只有一个线程从其他线程中写入消息。这与选项1和2没有太大区别,因为仍然是WriteCounter的责任仅发送一次数据。

4. 如果希望更进一步地进行过度设计,可以将consumer-producer与每个线程的缓冲区结合使用。可以通过以下方式定义一个public interface IBatchTextWriter

public interface IBatchTextWriter 
{
    TextWriter TextWriter { get; }
    
    /// 
    /// Ends the batch (everything written to the TextWriter will be written to the actual stream).
    void EndBatch();
}

并且可以像下面这样使用它:

var process = (ProcessInstance) obj;
batchWriter.TextWriter.WriteLine(
    $"Thread ID: {Thread.CurrentThread.ManagedThreadId}, " +
    $"Process Name: {process.ProcessName}, " +
    $"Process ID {process.ProcessId} ");
batchWriter.TextWriter.Write("\n");
foreach (var cnt in process.Counters)
{
    cnt.NextValue();
    batchWriter.TextWriter.WriteLine(
        $"ThreadId: {Thread.CurrentThread.ManagedThreadId} | " +
        $"Group: {cnt.CategoryName} | " +
        $"Process: {cnt.InstanceName} | " +
        $"Name: {cnt.CounterName} | " +
        $"Value: {cnt.GetCalculatedValue(cnt.CounterName)}");
    cnt.Close();
}
batchWriter.EndBatch();

附注:推荐阅读关于线程池和任务的文章-C# - ThreadPool vs Tasks

0