Asynchronous Progress Bar in Console Application

The following example demonstrate how to copy files from one directory to other directory asycnhronously. While copying files asynchronously, percentage of copied files are also updated asynchronously.

Notice the second picture, Total files count is 20. But, application could copy 17 files. Because, another process has been using those 3 files. I think you can solve this problem 🙂

if cancellation is needed, application can be cancelled by Ctrl + c keys. It is shown in the third picture.


using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace ProgressBar5
{
    public class Program
    {
        static int consoleTop = 1;
        static CancellationTokenSource cts;
        static string sourcePath = @"C:\";
        static string destinationPath = @"C:\TestCopy";
        static char[] progressCharacters = @"/-\|".ToArray();

        public static void Main(string[] args)
        {
            Console.WindowWidth = Console.LargestWindowWidth / 2;
            Console.WindowHeight = Console.LargestWindowHeight / 2;

            MainAsync().Wait();
        }

        static async Task MainAsync()
        {
            cts = new CancellationTokenSource();

            Console.CancelKeyPress += (s, e) =>
            {
                e.Cancel = true;

                cts.Cancel();
            };

            string[] files = Directory.GetFiles(sourcePath);

            if (Directory.Exists(destinationPath))
                Directory.Delete(destinationPath, true);

            Directory.CreateDirectory(destinationPath);

            List taskList = new List();
            Task copyTask;

            string msg = $@"Copying files from {sourcePath} directory to {destinationPath} directory.";
            Console.WriteLine(msg);
            Console.WriteLine(String.Join("", Enumerable.Repeat("-", msg.Length)));

            foreach (string sourceFilePath in files)
            {
                copyTask = CopyFileToDirectory(sourceFilePath, destinationPath, cts.Token);

                taskList.Add(copyTask);
            }

            await Task.WhenAll(taskList).ContinueWith((f, state) =>
            {
                List tList = (List)state;

                int successCount = tList.Count(ff => ff.Status == TaskStatus.RanToCompletion);


                Console.ForegroundColor = ConsoleColor.White;
                Console.SetCursorPosition(0, ++consoleTop);
                msg = $"Total file count is {files.Count()}.\nTotal copied file count is {successCount}.";
                Console.WriteLine(String.Join("", Enumerable.Repeat("-", msg.Length)));
                if (cts.IsCancellationRequested)
                {
                    Console.WriteLine("Application is cancelled.");
                }
                Console.WriteLine(msg);
            }, taskList);

            Console.ReadLine();
        }


        static async Task CopyFileToDirectory(string sourceFilePath, string destinationDirPath, CancellationToken token)
        {
            FileInfo sourceFi = new FileInfo(sourceFilePath);

            string message = $"Copying {sourceFi.Name} ";

            using (FileStream destinationFs = File.OpenWrite(Path.Combine(destinationDirPath, sourceFi.Name)))
            {
                using (FileStream sourceFs = sourceFi.OpenRead())
                {
                    //await sourceFs.CopyToAsync(destinationFs);

                    int bytesRead;
                    long totalBytesRead = 0;
                    byte[] bytesBuffer = new byte[8 * 1024];

                    long lengthOfSourceFile = sourceFi.Length;

                    int currentTop = Interlocked.Increment(ref consoleTop);

                    bool firstCall = true;

                    while ((bytesRead = await sourceFs.ReadAsync(bytesBuffer, 0, bytesBuffer.Length)) > 0)
                    {
                        if (token.IsCancellationRequested)
                        {
                            token.ThrowIfCancellationRequested();
                        }
                        await destinationFs.WriteAsync(bytesBuffer, 0, bytesRead);

                        totalBytesRead += bytesRead;

                        ShowProgress(firstCall, message, totalBytesRead, lengthOfSourceFile, currentTop);

                        //You can comment following line to increase copying speed.
                        //await Task.Delay(200);
                    }
                }
            }
        }

        static void ShowProgress(bool firstCall, string message, long processed, long total, int cursorTop)
        {
            long percent = (100 * (processed + 1)) / total;
            long characterIndex = (percent + 1) & 3;

            lock (typeof(Console))
            {
                int msgLenght = 0;
                if (firstCall)
                {
                    Console.ForegroundColor = ConsoleColor.White;
                    firstCall = false;
                    Console.SetCursorPosition(0, cursorTop);
                    string innerMsg = $"{message}";
                    msgLenght = innerMsg.Length;
                    Console.Write(innerMsg);
                }

                Console.SetCursorPosition(msgLenght, cursorTop);

                if (percent >= 100)
                {
                    Console.ForegroundColor = ConsoleColor.Green;
                    Console.Write($"{percent}% [OK]");
                }
                else
                {
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.Write($"{percent}% [{progressCharacters[characterIndex]}]");
                }
            }
        }
    }
}

2 thoughts on “Asynchronous Progress Bar in Console Application

Leave a Reply