webworm
webworm

Reputation: 11019

How to add events to C# windows application with long processing step

I have a windows form application that will have a primary task of reading through each line in a large text file and writing that line out to another file after performing some search and replace on that line. Since the files are very large >20 GB the process is expected to take a long time to complete.

The process of reading, modifying, and writing each line of the large text files will be time consuming so I would like to notify the user of the application of the progress during the process. It could be nothing more than counting the number of lines in the file and then dividing the current line number to get a percentage of work completed.

I know that this is where events and delegates come in but I have to admit I remain confused, even after reading many articles, on how to make use of this pattern. Any help in showing me how to add events to this app would be helpful.

Below is an example of what I am attempting to do. Essential replacing all values of t with x in the file.

WinForm class

namespace ParseFileTool
{
    public partial class FormMain : Form
    {
      public FormMain()
      {
          InitializeComponent();
      }

      private void btnStartProcessing_Click(object sender, EventArgs e)
      {
        ProcessFile pf = new ProcessFile();
        pf.InputFilePath = "PATH TO INPUT FILE";
        pf.OutputFilePath = "PATH TO OUTPUT FILE";
        pf.StartProcessing();
      }
    }
}

File Processor Class

class ProcessFile
{
    public string InputFilePath { get; set; }
    public string OutputFilePath { get; set; }

    public void StartProcessing()
    {
      using (StreamReader sr = new StreamReader(this.InputFilePath))
      {
        string line;
        while ((line = sr.ReadLine()) != null)
        {
          string newLine = line.Replace("t","x");
          using (FileStream fs = new FileStream(this.OutputFilePath, FileMode.Append, FileAccess.Write))
          using (StreamWriter sw = new StreamWriter(fs))
          {
            sw.WriteLine(newLine);
          }    
        }
      }
    }
}

I also wanted to know if I should be kicking off the instance of the ProcessFile class on a separate thread in order to allow the GUI to update properly or is this not necessary because events are to be used.

Thanks for any assistance. I would sure like to become comfortable with events and subscribing to them. I understand what a delegate is but I remain unsure where to declare it (the winform class or the ProcessFile class) and how to handle passing back data (like the line number processed) to the event handler.

Upvotes: 2

Views: 300

Answers (2)

Grant Winney
Grant Winney

Reputation: 66469

I like the BackgroundWorker for WinForms, although there's also the newer async/await model you can research as well.

Drop a BackgroundWorker onto your FormMain form and set ReportsProgress = true. Subscribe to the available events, for doing work and reporting progress.

Move anything that doesn't touch the UI into the DoWork event, which runs on a separate thread. Modify your loop so it reports progress at some regular interval.

public void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
  var inputFilePath = "input file path";
  var outputFilePath = "output file Path";

  using (var sr = new StreamReader(inputFilePath))
  {
    int counter = 0;

    string line;
    while ((line = sr.ReadLine()) != null)
    {
      // do all the important stuff

      counter++;

      // report progress after every 100 lines
      // reporting too often can essentially lock the UI by continuously updating it
      if (counter % 100 == 0)
        backgroundWorker1.ReportProgress(counter);
    }
  }
}

The ProgressChanged event runs back on the UI thread, so you can accept whatever the DoWork event passed to it and display that to the user.

private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
  lblProgress.Text = string.Format("Processed {0} lines.", e.ProgressPercentage);
}

You'll probably want to subscribe to the RunWorkerCompleted event too...

void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
  lblProgress.Text = "Job finished!";
}

Start it by calling:

backgroundWorker1.RunWorkerAsync();

Upvotes: 1

Alexander Polyankin
Alexander Polyankin

Reputation: 1887

You need to use background thread. Many options are here, but most simple is:

Task.Run(() => pf.StartProcessing());

Upvotes: 0

Related Questions