Индикатор выполнения в консольном приложении


84

Я пишу простое консольное приложение С #, которое загружает файлы на сервер sftp. Однако количество файлов велико. Я хотел бы отображать либо процент загруженных файлов, либо просто количество уже загруженных файлов от общего количества файлов, которые нужно загрузить.

Сначала я получаю все файлы и общее количество файлов.

string[] filePath = Directory.GetFiles(path, "*");
totalCount = filePath.Length;

Затем я просматриваю файл и загружаю их один за другим в цикле foreach.

foreach(string file in filePath)
{
    string FileName = Path.GetFileName(file);
    //copy the files
    oSftp.Put(LocalDirectory + "/" + FileName, _ftpDirectory + "/" + FileName);
    //Console.WriteLine("Uploading file..." + FileName);
    drawTextProgressBar(0, totalCount);
}

В цикле foreach у меня есть индикатор выполнения, с которым у меня проблемы. Он не отображается должным образом.

private static void drawTextProgressBar(int progress, int total)
{
    //draw empty progress bar
    Console.CursorLeft = 0;
    Console.Write("["); //start
    Console.CursorLeft = 32;
    Console.Write("]"); //end
    Console.CursorLeft = 1;
    float onechunk = 30.0f / total;

    //draw filled part
    int position = 1;
    for (int i = 0; i < onechunk * progress; i++)
    {
        Console.BackgroundColor = ConsoleColor.Gray;
        Console.CursorLeft = position++;
        Console.Write(" ");
    }

    //draw unfilled part
    for (int i = position; i <= 31 ; i++)
    {
        Console.BackgroundColor = ConsoleColor.Green;
        Console.CursorLeft = position++;
        Console.Write(" ");
    }

    //draw totals
    Console.CursorLeft = 35;
    Console.BackgroundColor = ConsoleColor.Black;
    Console.Write(progress.ToString() + " of " + total.ToString() + "    "); //blanks at the end remove any excess
}

На выходе всего [] 0 из 1943 г.

Что я здесь делаю не так?

РЕДАКТИРОВАТЬ:

Я пытаюсь отобразить индикатор выполнения при загрузке и экспорте файлов XML. Однако он проходит через петлю. После завершения первого раунда он переходит ко второму и так далее.

string[] xmlFilePath = Directory.GetFiles(xmlFullpath, "*.xml");
Console.WriteLine("Loading XML files...");
foreach (string file in xmlFilePath)
{
     for (int i = 0; i < xmlFilePath.Length; i++)
     {
          //ExportXml(file, styleSheet);
          drawTextProgressBar(i, xmlCount);
          count++;
     }
 }

Он никогда не выходит из цикла for ... Есть предложения?


Что такое xmlCount и count?
eddie_cat

Считайте просто приращение. xmlCount - это просто общее количество файлов XML в указанной папке DirectoryInfo xmlDir = new DirectoryInfo (xmlFullpath); xmlCount = xmlDir.GetFiles (). Длина;
smr5

1
Кроме того, почему цикл for находится внутри цикла foreach? Кажется, повторяется одно и то же. Вероятно, нет необходимости сохранять цикл foreach.
eddie_cat

1
Вы удалили внешний цикл foreach? Изменение закомментированного бита наExportXml(xmlFilePath[i])
eddie_cat

1
Вот и все. У меня есть только цикл for, и он работает.
smr5

Ответы:


11

Эта строка - ваша проблема:

drawTextProgressBar(0, totalCount);

Вы говорите, что прогресс равен нулю на каждой итерации, его следует увеличивать. Возможно, вместо этого используйте цикл for.

for (int i = 0; i < filePath.length; i++)
{
    string FileName = Path.GetFileName(filePath[i]);
    //copy the files
    oSftp.Put(LocalDirectory + "/" + FileName, _ftpDirectory + "/" + FileName);
    //Console.WriteLine("Uploading file..." + FileName);
    drawTextProgressBar(i, totalCount);
}

Это сработало в первый раз, и я делаю то же самое в другом месте того же самого, и это вызывает петлю. Это никогда не прекращается. Я обновил свой пост. Вы можете взглянуть на это? Благодарю.
smr5

Что обновляли? Мне это кажется таким же, что мне не хватает?
eddie_cat

200

Я также искал индикатор выполнения консоли. Я не нашел ни одного, который делал бы то, что мне нужно, поэтому решил попробовать себя. Щелкните здесь, чтобы увидеть исходный код (лицензия MIT).

Анимированный индикатор выполнения

Особенности:

  • Работает с перенаправленным выводом

    Если вы перенаправите вывод консольного приложения (например, Program.exe > myfile.txt), большинство реализаций выйдет из строя с исключением. Это потому , что Console.CursorLeftи Console.SetCursorPosition()не поддерживает перенаправлен выход.

  • Орудия IProgress<double>

    Это позволяет вам использовать индикатор выполнения с асинхронными операциями, которые сообщают о ходе выполнения в диапазоне [0..1].

  • Потокобезопасный

  • Быстрый

    ConsoleКласс славится своей производительности бездонной. Слишком много обращений к нему, и ваше приложение тормозит. Этот класс выполняет только 8 вызовов в секунду, независимо от того, как часто вы сообщаете о ходе обновления.

Используйте это так:

Console.Write("Performing some task... ");
using (var progress = new ProgressBar()) {
    for (int i = 0; i <= 100; i++) {
        progress.Report((double) i / 100);
        Thread.Sleep(20);
    }
}
Console.WriteLine("Done.");

4
Выглядит довольно аккуратно! Не могли бы вы добавить к нему лицензию OSS, например MIT? choosealicense.com
Дэниел Плейстед

2
Хорошая идея. Готово.
Daniel Wolf

@DanielWolf, как вы получили Console.Write от изменения CursorPosition?
JJS

1
@knocte: В производственном коде я бы определенно стал. Целью здесь было сделать пример как можно более лаконичным и не отвлекать от важных частей.
Дэниел Вольф

8
Гифка привлекательна.
Лэй Ян

18

Я знаю, что это старый поток, и извиняюсь за саморекламу, однако недавно я написал консольную библиотеку с открытым исходным кодом, доступную на nuget Goblinfactory.Konsole с поддержкой многопоточной поддержки нескольких индикаторов выполнения, которая может помочь любому новичку на этой странице, нуждающемуся в такой, которая не блокирует основной поток.

Это несколько отличается от ответов выше, поскольку позволяет запускать загрузки и задачи параллельно и продолжать другие задачи;

ура, надеюсь, это будет полезно

А

var t1 = Task.Run(()=> {
   var p = new ProgressBar("downloading music",10);
   ... do stuff
});

var t2 = Task.Run(()=> {
   var p = new ProgressBar("downloading video",10);
   ... do stuff
});

var t3 = Task.Run(()=> {
   var p = new ProgressBar("starting server",10);
   ... do stuff .. calling p.Refresh(n);
});

Task.WaitAll(new [] { t1,t2,t3 }, 20000);
Console.WriteLine("all done.");

дает вам этот тип вывода

введите описание изображения здесь

Пакет nuget также включает утилиты для записи в оконный раздел консоли с полной поддержкой отсечения и упаковки, а также PrintAtразличные другие полезные классы.

Я написал пакет nuget, потому что мне постоянно приходилось писать множество общих консольных подпрограмм всякий раз, когда я писал скрипты и утилиты для сборки и эксплуатации консоли.

Если я скачивал несколько файлов, то медленно Console.Write на экран в каждом потоке и пробовал различные уловки, чтобы облегчить чтение чередующегося вывода на экране, например, разных цветов или чисел. В конце концов я написал библиотеку управления окнами, чтобы вывод из разных потоков можно было просто распечатать в разных окнах, и это сократило количество шаблонного кода в моих служебных скриптах.

Например, этот код,

        var con = new Window(200,50);
        con.WriteLine("starting client server demo");
        var client = new Window(1, 4, 20, 20, ConsoleColor.Gray, ConsoleColor.DarkBlue, con);
        var server = new Window(25, 4, 20, 20, con);
        client.WriteLine("CLIENT");
        client.WriteLine("------");
        server.WriteLine("SERVER");
        server.WriteLine("------");
        client.WriteLine("<-- PUT some long text to show wrapping");
        server.WriteLine(ConsoleColor.DarkYellow, "--> PUT some long text to show wrapping");
        server.WriteLine(ConsoleColor.Red, "<-- 404|Not Found|some long text to show wrapping|");
        client.WriteLine(ConsoleColor.Red, "--> 404|Not Found|some long text to show wrapping|");

        con.WriteLine("starting names demo");
        // let's open a window with a box around it by using Window.Open
        var names = Window.Open(50, 4, 40, 10, "names");
        TestData.MakeNames(40).OrderByDescending(n => n).ToList()
             .ForEach(n => names.WriteLine(n));

        con.WriteLine("starting numbers demo");
        var numbers = Window.Open(50, 15, 40, 10, "numbers", 
              LineThickNess.Double,ConsoleColor.White,ConsoleColor.Blue);
        Enumerable.Range(1,200).ToList()
             .ForEach(i => numbers.WriteLine(i.ToString())); // shows scrolling

производит это

введите описание изображения здесь

Вы также можете создавать индикаторы выполнения внутри окна так же легко, как писать в окнах. (смешивать и сочетать).


Это просто лучший
Pratik

9

Вы можете попробовать https://www.nuget.org/packages/ShellProgressBar/

Я только что наткнулся на эту реализацию индикатора выполнения - это кроссплатформенный, очень простой в использовании, достаточно настраиваемый и делает то, что должен, прямо из коробки.

Просто поделился, потому что мне это очень понравилось.


6

Я скопировал ваш ProgressBarметод. Поскольку ваша ошибка была в цикле, как упоминалось в принятом ответе. Но и в ProgressBarметоде есть синтаксические ошибки. Вот рабочая версия. Немного доработан.

private static void ProgressBar(int progress, int tot)
{
    //draw empty progress bar
    Console.CursorLeft = 0;
    Console.Write("["); //start
    Console.CursorLeft = 32;
    Console.Write("]"); //end
    Console.CursorLeft = 1;
    float onechunk = 30.0f / tot;

    //draw filled part
    int position = 1;
    for (int i = 0; i < onechunk * progress; i++)
    {
        Console.BackgroundColor = ConsoleColor.Green;
        Console.CursorLeft = position++;
        Console.Write(" ");
    }

    //draw unfilled part
    for (int i = position; i <= 31; i++)
    {
        Console.BackgroundColor = ConsoleColor.Gray;
        Console.CursorLeft = position++;
        Console.Write(" ");
    }

    //draw totals
    Console.CursorLeft = 35;
    Console.BackgroundColor = ConsoleColor.Black;
    Console.Write(progress.ToString() + " of " + tot.ToString() + "    "); //blanks at the end remove any excess
}

Обратите внимание, что у @ Daniel-wolf есть лучший подход: https://stackoverflow.com/a/31193455/169714


6

Я создал этот удобный класс, который работает с System.Reactive. Я надеюсь, тебе это понравится.

public class ConsoleDisplayUpdater : IDisposable
{
    private readonly IDisposable progressUpdater;

    public ConsoleDisplayUpdater(IObservable<double> progress)
    {
        progressUpdater = progress.Subscribe(DisplayProgress);
    }

    public int Width { get; set; } = 50;

    private void DisplayProgress(double progress)
    {
        if (double.IsNaN(progress))
        {
            return;
        }

        var progressBarLenght = progress * Width;
        System.Console.CursorLeft = 0;
        System.Console.Write("[");
        var bar = new string(Enumerable.Range(1, (int) progressBarLenght).Select(_ => '=').ToArray());

        System.Console.Write(bar);

        var label = $@"{progress:P0}";
        System.Console.CursorLeft = (Width -label.Length) / 2;
        System.Console.Write(label);
        System.Console.CursorLeft = Width;
        System.Console.Write("]");
    }

    public void Dispose()
    {
        progressUpdater?.Dispose();
    }
}

5

Мне очень понравился индикатор выполнения оригинального плаката, но я обнаружил, что он некорректно отображает прогресс при определенных комбинациях прогресса / общего количества элементов. Следующее, например, не отрисовывается правильно, оставляя лишний серый блок в конце индикатора выполнения:

drawTextProgressBar(4114, 4114)

Я переделал часть кода рисования, чтобы удалить ненужные циклы, которые устранили указанную выше проблему, а также немного ускорили процесс:

public static void drawTextProgressBar(string stepDescription, int progress, int total)
{
    int totalChunks = 30;

    //draw empty progress bar
    Console.CursorLeft = 0;
    Console.Write("["); //start
    Console.CursorLeft = totalChunks + 1;
    Console.Write("]"); //end
    Console.CursorLeft = 1;

    double pctComplete = Convert.ToDouble(progress) / total;
    int numChunksComplete = Convert.ToInt16(totalChunks * pctComplete);

    //draw completed chunks
    Console.BackgroundColor = ConsoleColor.Green;
    Console.Write("".PadRight(numChunksComplete));

    //draw incomplete chunks
    Console.BackgroundColor = ConsoleColor.Gray;
    Console.Write("".PadRight(totalChunks - numChunksComplete));

    //draw totals
    Console.CursorLeft = totalChunks + 5;
    Console.BackgroundColor = ConsoleColor.Black;

    string output = progress.ToString() + " of " + total.ToString();
    Console.Write(output.PadRight(15) + stepDescription); //pad the output so when changing from 3 to 4 digits we avoid text shifting
}

это работает в целом, за исключением того, что он удаляет предыдущие выходные данные консоли, как любой текст до этого, и что он не переходит на новую строку после ....
Дэвид Шнайдер

0

Я просто наткнулся на эту ветку в поисках чего-то еще, и подумал, что оставлю свой код, который я собрал, который загружает список файлов с помощью DownloadProgressChanged. Я считаю это очень полезным, поэтому я не только вижу прогресс, но и фактический размер файла. Надеюсь, это кому-то поможет!

public static bool DownloadFile(List<string> files, string host, string username, string password, string savePath)
    {
        try
        {
            //setup FTP client

            foreach (string f in files)
            {
                FILENAME = f.Split('\\').Last();
                wc.DownloadFileCompleted += new AsyncCompletedEventHandler(Completed);
                wc.DownloadProgressChanged += new DownloadProgressChangedEventHandler(ProgressChanged);
                wc.DownloadFileAsync(new Uri(host + f), savePath + f);
                while (wc.IsBusy)
                    System.Threading.Thread.Sleep(1000);
                Console.Write("  COMPLETED!");
                Console.WriteLine();
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
            return false;
        }
        return true;
    }

    private static void ProgressChanged(object obj, System.Net.DownloadProgressChangedEventArgs e)
    {
        Console.Write("\r --> Downloading " + FILENAME +": " + string.Format("{0:n0}", e.BytesReceived / 1000) + " kb");
    }

    private static void Completed(object obj, AsyncCompletedEventArgs e)
    {
    }

Вот пример вывода: введите описание изображения здесь

Надеюсь, это кому-то поможет!


2
@regisbsb Это не индикаторы выполнения, похоже, он подверг цензуре часть имен файлов :) Знаю, меня сначала тоже обманули.
silkfire

-1

Я все еще новичок, C#но считаю, что приведенное ниже может помочь.

string[] xmlFilePath = Directory.GetFiles(xmlFullpath, "*.xml");
Console.WriteLine("Loading XML files...");
int count = 0;
foreach (string file in xmlFilePath)
{
    //ExportXml(file, styleSheet);
    drawTextProgressBar(count, xmlCount);
    count++;
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.