c# 控制台console進度條


1 說明

筆者大多數的開發在 Linux 下,多處用到進度條的場景,但又無需用到圖形化界面,所以就想着弄個 console 下的進度條顯示。

2 步驟

  1. 清行顯示

    //清行處理操作
    int currentLineCursor = Console.CursorTop;//記錄當前光標位置
    Console.SetCursorPosition(0, Console.CursorTop);//將光標至於當前行的開始位置
    Console.Write(new string(' ', Console.WindowWidth));//用空格將當前行填滿,相當於清除當前行
    Console.SetCursorPosition(0, currentLineCursor);//將光標恢復至開始時的位置
    

    如果要清除上一行,只需在清行處理操作前將調整光標位置提前一行,即:Console.SetCursorPosition(0, Console.CursorTop - 1);。緊接着Console.WriteLine(/*something*/);,即可實現在控制台末尾行重復輸出。

  2. 多線程下的輸出顯示

    多線程下最容易出現的問題就是一個線程的輸出覆蓋其他線程的輸出,或者是一個線程緊跟着上一個線程輸出而沒有換行,這些情況多會導致輸出的混亂。為了,解決這樣的問題,特意實現一個專門的輸出類來進行輸出顯示:

     public class Consoler
     {
         private static string lastContext = "";//用於記錄上次寫的內容
         private static readonly object _lock = new object();//加鎖保證只有一個輸出
         public static void Write(string context)
         {
             lastContext = context;//記錄上次寫的內容
             lock (_lock)
             {
                 Console.Write(context);
             }
    
         }
         /// <summary>
         /// 覆寫
         /// </summary>
         /// <param name="context"></param>
         public static void OverWrite(string context = null)
         {
             lastContext = context;//記錄上次寫的內容
             var strLen = context?.Length ?? 0;
             //空白格的長度,考慮到內容可能超出一行的寬度,所以求余。
             var blankLen = Console.WindowWidth - strLen % Console.WindowWidth - 1;
             var rowCount = strLen / Console.WindowWidth;
             Console.SetCursorPosition(0, Console.CursorTop - rowCount);
             //空白只需填充最后一行的剩余位置即可。
             lock (_lock)
             {
                 Console.Write(context + new string(' ', blankLen));
             }
         }
    
         public static void WriteLine(string context = null)
         {
             ClearConsoleLine();//清除最后一行
             lock (_lock)
             {
                 Console.WriteLine(context);
                 if (!string.IsNullOrWhiteSpace(lastContext))
                     Console.Write(lastContext);//重新輸出最后一次的內容,否則有較明顯的閃爍
                 lastContext = null;
             }
         }
    
         public static void ClearConsoleLine(int invertedIndex = 0)
         {
             int currentLineCursor = Console.CursorTop;
             int top = Console.CursorTop - invertedIndex;
             top = top < 0 ? 0 : top;
             Console.SetCursorPosition(0, top);
             Console.Write(new string(' ', Console.WindowWidth - 1));
             Console.SetCursorPosition(0, currentLineCursor);
         }
     }
    

    實際測試時,使用多 Task (模擬多線程)去進行輸出實驗:

    static void Main(string[] args)
     {
         Console.WriteLine("Hello World!");
    
         var task1 = Task.Run(() =>
         {
             int count = 0, w, lw, rw;
             float p = 0;
             while (true)
             {
                 w = (int)(Console.WindowWidth * 0.6);
                 count %= 75;
                 p = count++ / 74f;
                 lw = (int)(p * w);
                 rw = w - lw;
                 Consoler.OverWrite($"from task1, [{new string('#', lw) + new string(' ', rw)}]:{p:#.00%}");
                 Thread.Sleep(100);
             }
         });
         var task2 = Task.Run(() =>
         {
             while (true)
             {
                 Consoler.WriteLine($"from task2, now:{DateTime.Now}");
                 Thread.Sleep(5000);
             }
         });
    
         var task3 = Task.Run(() =>
         {
             var rd = new Random();
             while (true)
             {
                 Consoler.WriteLine($"from task3, {new string('+', (int)(rd.NextDouble() * Console.WindowWidth))}");
                 Thread.Sleep(rd.Next(5000));
             }
         });
         Task.WaitAll(task1);
     }
    

    最終輸出結果:

     from task2, now:6/5/19 8:10:24 PM
     from task3, +++++++++++++++++++++++++++++++++
     from task2, now:6/5/19 8:10:29 PM
     from task3, +++++++++++++++++++++++++
     from task3, ++++
     from task2, now:6/5/19 8:10:34 PM
     from task3, +++++++++++++++++++++++
     from task3, ++++++++++++
     from task3, ++++++
     from task2, now:6/5/19 8:10:44 PM
     from task1, [###########################                     ]:58.11%
    

    task1 用來進行進度條的輸出,task2 和 task3 進行隨機輸出。可以看出,task1 永遠在最后一行進行進度更新,其他輸出任然可以正常進行。實現的效果和 ubuntu 下執行更新命令sudo apt-get update的輸出類似。

  3. 總結

    雖然該例子是在 c#下完成的,但在 python,c,java 中通用。為了保證輸出的有序性,程序中加了鎖,影像了多線程的效率,但是對於界面顯示是足夠的。如果需要高性能,那么考慮使用類似於隊列式的異步更新輸出顯示的方法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM