从多个线程更新控制台文本不起作用

Update console text from multiple threads not working

我在控制台应用程序的多线程模式下 executing/processing 非常大的文件。

当我不update/write从线程到控制台时,测试整个过程大约需要 1 分钟。

但是,当我尝试 update/write 从线程进行控制台以显示进度时,进程卡住了并且永远不会完成(等待几分钟甚至几小时)。而且控制台 text/window 没有更新。

Update-1: 应少数响应者的要求,我添加了可以重现相同内容的最少代码 error/problem

这是线程 function/method:

中的代码
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace Large_Text_To_Small_Text
{
class Program
{
    static string sAppPath;
    static ArrayList objThreadList;

    private struct ThreadFileInfo
    {
        public string sBaseDir, sRFile;
        public int iCurFile, iTFile;
        public bool bIncludesExtension;
    }

    static void Main(string[] args)
    {
        string sFileDir;
        DateTime dtStart;


        Console.Clear();
        sAppPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
        sFileDir = @"d:\Test";
        dtStart = DateTime.Now;
        ///process in multi threaded mode
        List<string> lFiles;

        lFiles = new List<string>();
        lFiles.AddRange(Directory.GetFiles(sFileDir, "*.*", SearchOption.AllDirectories));
        if (Directory.Exists(sFileDir + "-Processed") == true)
        {
            Directory.Delete(sFileDir + "-Processed", true);
        }
        Directory.CreateDirectory(sFileDir + "-Processed");
        sPrepareThreading();
        for (int iFLoop = 0; iFLoop < lFiles.Count; iFLoop++)
        {
            //Console.WriteLine(string.Format("{0}/{1}", (iFLoop + 1), lFiles.Count));
            sThreadProcessFile(sFileDir + "-Processed", lFiles[iFLoop], (iFLoop + 1), lFiles.Count, Convert.ToBoolean(args[3]));

        }
        sFinishThreading();

        Console.WriteLine(DateTime.Now.Subtract(dtStart).ToString());
        Console.ReadKey();
        return;
    }

    private static void sProcSO(object oThreadInfo)
    {
        var inputLines = new BlockingCollection<string>();
        long lACounter, lCCounter;
        ThreadFileInfo oProcInfo;

        lACounter = 0;
        lCCounter = 0;
        oProcInfo = (ThreadFileInfo)oThreadInfo;
        var readLines = Task.Factory.StartNew(() =>
        {
            foreach (var line in File.ReadLines(oProcInfo.sRFile))
            {
                inputLines.Add(line);
                lACounter++;
            }
            inputLines.CompleteAdding();
        });

        var processLines = Task.Factory.StartNew(() =>
        {
            Parallel.ForEach(inputLines.GetConsumingEnumerable(), line =>
            {
                lCCounter++;
                /*
                    some process goes here
                */

                /*If i Comment out these lines program get stuck!*/
                //Console.SetCursorPosition(0, oProcInfo.iCurFile);
                //Console.Write(oProcInfo.iCurFile + " = " + lCCounter.ToString());
            });
        });

        Task.WaitAll(readLines, processLines);
    }

    private static void sPrepareThreading()
    {
        objThreadList = new ArrayList();
        for (var iTLoop = 0; iTLoop < 5; iTLoop++)
        {
            objThreadList.Add(null);
        }
    }

    private static void sThreadProcessFile(string sBaseDir, string sRFile, int iCurFile, int iTFile, bool bIncludesExtension)
    {
        Boolean bMatched;
        Thread oCurThread;
        ThreadFileInfo oProcInfo;

    Salma_RecheckThread:
        bMatched = false;
        for (int iTLoop = 0; iTLoop < 5; iTLoop++)
        {
            if (objThreadList[iTLoop] == null || ((System.Threading.Thread)(objThreadList[iTLoop])).IsAlive == false)
            {
                oProcInfo = new ThreadFileInfo()
                {
                    sBaseDir = sBaseDir,
                    sRFile = sRFile,
                    iCurFile = iCurFile,
                    iTFile = iTFile,
                    bIncludesExtension = bIncludesExtension
                };
                oCurThread = new Thread(sProcSO);
                oCurThread.IsBackground = true;
                oCurThread.Start(oProcInfo);
                objThreadList[iTLoop] = oCurThread;
                bMatched = true;
                break;
            }
        }
        if (bMatched == false)
        {
            System.Threading.Thread.Sleep(250);
            goto Salma_RecheckThread;
        }
    }


    private static void sFinishThreading()
    {
        Boolean bRunning;
    Salma_RecheckThread:
        bRunning = false;
        for (int iTLoop = 0; iTLoop < 5; iTLoop++)
        {
            if (objThreadList[iTLoop] != null && ((System.Threading.Thread)(objThreadList[iTLoop])).IsAlive == true)
            {
                bRunning = true;
            }
        }
        if (bRunning == true)
        {
            System.Threading.Thread.Sleep(250);
            goto Salma_RecheckThread;
        }
    }

}
}

这是屏幕截图,如果我尝试更新控制台 window:

看到了吗?行号(oProcInfo.iCurFile)或整行都不正确!

应该是这样的:

1 = xxxxx
2 = xxxxx
3 = xxxxx
4 = xxxxx
5 = xxxxx

Update-1: 要测试,只需将 sFileDir 更改为任何包含大文本文件的文件夹,或者如果您愿意,可以从以下网址下载一些大文本文件 link:

https://wetransfer.com/downloads/8aecfe05bb44e35582fc338f623ad43b20210602005845/bcdbb5

我是否缺少任何 function/method 从线程更新控制台文本?

您的冻结问题可能与 C# 或代码无关

在控制台的左上角 window,在图标上 .. 右键单击 select Properties 删除 Quick Edit ModeInsert Mode

的选项

您可以 google 该功能,但本质上体现在您上面描述的问题中

另一方面,格式问题似乎确实存在,在这里您需要创建一个 class 来序列化从单个线程写入控制台 window。 consumer/producer 模式可以工作(您可以使用 BlockingCollection 来很容易地实现它)

我无法重现。在我的测试中,该过程始终运行到完成,而不会卡住。虽然输出到处都是,因为下面的两行不同步:

Console.SetCursorPosition(0, oProcInfo.iCurFile);
Console.Write(oProcInfo.iCurFile + " = " + lCCounter.ToString());

参与计算的许多线程中的每个线程与其他线程同时调用这两个语句。这使得一个线程可以抢占另一个线程,并在第一个线程有机会在控制台中写入之前移动光标。要解决此问题,您必须添加适当的同步,最简单的方法是使用 lock 语句:

class Program
{
    static object _locker = new object();

并且在 sProcSO 方法中:

lock (_locker)
{
    Console.SetCursorPosition(0, oProcInfo.iCurFile);
    Console.Write(oProcInfo.iCurFile + " = " + lCCounter.ToString());
}

如果你想了解更多关于线程同步的知识,我推荐这个在线资源:Threading in C# - Part 2: Basic Synchronization

如果您想听听我对问题中代码的看法,并且您不介意受到批评,我的意见是老实说,代码问题百出,最好的做法是扔掉它并从头开始。使用古老的数据结构 (ArrayList???),自由使用从 object 到特定类型的转换,自由使用 goto 语句,在 [=35= 中使用匈牙利符号] 类型成员,所有这些都使代码难以遵循,并且容易出现错误。我发现特别有问题的是每个文件都使用专用线程与所有其他文件同时处理,然后每个专用线程使用 ThreadPool 线程 (Task.Factory.StartNew) 以未配置的 MaxDegreeOfParallelism 启动并行循环 (Parallel.ForEach)。此设置可确保 ThreadPool 严重饱和,以至于线程的可用性永远无法满足需求。很可能它还会导致存储设备的使用效率极低,特别是如果硬件是经典硬盘。