-1

I'm currently working on a program that converts a list of files from .ps (PostScript) to .png.

Originally, this was done in a batch file, one file at a time. I am working on code that uses the Ghostscript.NET dll to process these files asynchronously. By splitting these up into tasks, I have cut down the processing time from 30 minutes to about 6 minutes.

I want to be able to show the user some sort of progress on this, so that it doesn't just look like my program is frozen.

I know just enough about threading to frustrate myself, so any suggestions on the best way to do this is greatly appreciated. The code below has a BackgroundWorker implemented to try to show the progress. I have used BGWorker before to show progress, but not on multiple tasks like this. In fact, this is my first time multi-threading without just using BGWorker.

I feel that BGWorker is probably not what I need to be using, but I wanted to try to take a stab at it myself before I asked.

Here is the code that I have so far:

public partial class ProcessStatusForm : Form
{
    public string[] testList;
    public string wordPath;
    public string StatusText;
    public GhostscriptVersionInfo _gs_version_info;
    public DirectoryInfo dInfo;
    public List<Task> tasks;
    public float NumberOfTasks;
    public bool PS2PNGRunning;
    public int ProgressPct;
    public float dPercent;
    public decimal decPercent;

    public ProcessStatusForm(string wordDoc, List<string> runList)
    {
        InitializeComponent();
        this.wordPath = wordDoc;
        this.testList = runList.ToArray();
        this.StatusText = string.Empty;
        this._gs_version_info = GhostscriptVersionInfo.GetLastInstalledVersion(GhostscriptLicense.GPL |
            GhostscriptLicense.AFPL, GhostscriptLicense.GPL);
        this.dInfo = new DirectoryInfo(SettingsClass.PSFolder);
        this.PS2PNGRunning = false;
        this.ProgressPct = 0;
        this.NumberOfTasks = runList.Count;
    }

    private void ProcessStatusForm_Shown(object sender, EventArgs e)
    {
        //Spawn tasks for each of the .ps files in the PS_FILES folder
        tasks = new List<Task>(dInfo.GetFiles("*.ps").Length);

        //Start the BackgroundWorker
        this.PS2PNGRunning = true;
        BackgroundWorker.RunWorkerAsync();

        foreach (var file in dInfo.GetFiles("*.ps"))
        {
            //Get fileName to pass fo the ConvertPS2PNG
            string inputFile = file.Name;

            //Create the Task
            var task = Task.Factory.StartNew(() => ConvertPS2PNG(inputFile));
            tasks.Add(task);
        }
        //Wait until all tasks have completed
        Task.WaitAll(tasks.ToArray());
        PS2PNGRunning = false;
    }

    private void ConvertPS2PNG(string input)
    {
        string output = input.Replace(".ps", "_01.png");
        input = SettingsClass.PSFolder + input;
        output = SettingsClass.PNGFolder + output;
        GhostscriptProcessor processor = new GhostscriptProcessor(_gs_version_info, true);
        processor.Process(CreateGSArgs(input, output), new ConsoleStdIO(true, true, true));
    }

    private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        ProgressPct = 0;
        while (PS2PNGRunning)
        {
            Thread.Sleep(1000);
            float TasksCompleted = 0;
            foreach (var tsk in tasks)
            {
                if (tsk.Status == TaskStatus.RanToCompletion)
                {
                    TasksCompleted++;
                }
            }
            StatusText = TasksCompleted + " of " + NumberOfTasks + " converted...";
            dPercent = TasksCompleted / NumberOfTasks;
            dPercent *= 100;
            decPercent = (decimal)dPercent;
            decPercent = Math.Round(decPercent);
            ProgressPct = (int)decPercent;
            BackgroundWorker.ReportProgress(ProgressPct);
        }
        BackgroundWorker.ReportProgress(100);
    }

    private void BackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        this.ProgressLabel.Text = this.StatusText;
        this.progressBar.Style = ProgressBarStyle.Continuous;
        this.progressBar.Value = e.ProgressPercentage;
    }

    public string[] CreateGSArgs(string inPath, string outPath)
    {
        List<string> gsArgs = new List<string>();

        gsArgs.Add("-dBATCH");
        gsArgs.Add("-dNOPAUSE");
        gsArgs.Add("-sDEVICE=png16m");
        gsArgs.Add("-dQUIET");
        gsArgs.Add("-sPAPERSIZE=letter");
        gsArgs.Add("-r800");
        gsArgs.Add("-sOutputFile=" + outPath);
        gsArgs.Add(inPath);

        return gsArgs.ToArray();
    }
}

When I put breaks in the code of BackgroundWorker_DoWork, everything seems to be coming out right, but when it gets to the BackgroundWorker.ReportProgress(), it never makes it to the BackgroundWorker_ProgressChanged() method.

At the very least, I could live with just having a progressBar.Style as marquee while this is running so that the user can see that the program is working, but reporting the actual progress would be ideal.

As I said before, I haven't done a ton of work with threading, and all of my knowledge on the subject pretty much comes from Google and StackOverflow. If there is a completely different way to do this, I am open to all criticism.

Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
SimTrooper
  • 92
  • 8
  • What are you using for your UI? Are you using Winforms or WPF? – CalebB Apr 10 '15 at 19:32
  • I'm just using WinForms for this. I can use WPF if there's a better or easier way to do this with that method, as I just started developing it. – SimTrooper Apr 10 '15 at 19:33
  • You might want to set up a dispatch timer to keep the UI updated instead of that event. Either that or the event may not be getting raised so you could try carrying out the change to the progress and such from the window's dispatcher: `this.Dispatcher.Invoke(new Action(() => {"Do work here""}));` this should raise these changes to the UI. – CalebB Apr 10 '15 at 19:39
  • I've never used Dispatcher before. Do I need to change to WPF to use this? All of the material I'm seeing on the subject seems to be done through WPF. – SimTrooper Apr 10 '15 at 19:57
  • Most often it is used under the context of WPF and WPF, in my opinion, does offer a great deal more flexibility than Winforms but the use of Dispatcher should be independent of WPF windows. Do some research on Dispatcher, the documentation will give a great deal more detailed information. – CalebB Apr 10 '15 at 20:02
  • Where is your BackgroundWorker declared/defined? – Shar1er80 Apr 10 '15 at 20:03
  • The BackgroundWorker was just dragged into the form from the designer screen, and I changed the WorkerReportsProgress to true. – SimTrooper Apr 10 '15 at 20:07
  • Why are you mixing up Tasks and BackgroundWorker? Tasks completely replace the worker in all scenarios and result in far cleaner code. In your case, it doesn't even do anything useful, just blocks a background thread. If you want to report progress, use the Progress class, or use a continuation after each actual task to report completion – Panagiotis Kanavos Apr 17 '15 at 14:28

1 Answers1

0

Was the name BackgroundWorker given to the object when you dragged it from the designer screen? If not change your code to use the appropriate name it was given (default should have been backgroundWorker1).

Or...

Try casting the sender object to a BackgroundWorker object in your DoWork method and call ReportProgress() from there.

private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker bw = sender as BackgroundWorker;
    if (bw != null)
    {
        bw.ReportProgress(25);
    }
}
Shar1er80
  • 9,001
  • 2
  • 20
  • 29