title: How to Properly Implement BackgroundWorker in WPF with MVVM / ICommand Pattern
body: <p>I have a small WPF application written primarily following MVVM pattern. The point of the program is to read the lines of a text file, parse the data from them, write that data to a list of objects, then write the data in those objects into a specifically-formatted .CSV file.</p>
  
  <p>Even though I have implemented the BackgroundWorker class itself the same way I always have with other applications, this time I am invoking the RunWorkAsync() method from within the Execute() method of my ICommand. While the final output is correct and the application actually delivers the desired result, the UI STILL locks up while the BackgroundWorker is running.</p>
  
  <p>I have wrapped my BackgroundWorker members and all the logic inside a class named "ReaderWriter" with a constructor that takes my ViewModel as a parameter.</p>
  
  <p>By calling the "StartProcess" method of my ReaderWriter instance, the BackgroundWorker's RunWorkerAsync() is called -- this is where I was hoping it would take over on another thread and do my long-running process of reading the source file, parsing the data, and writing the new file; all while periodically doing ReportProgress() to update the ProgressBar.</p>
  
  <p>Here is the code for my ReaderWriter class:</p>
  
  <pre><code>class ReaderWriter
  {
      private LogDataViewModel vm { get; set; }
      private BackgroundWorker bw { get; set; }
  
      public ReaderWriter(LogDataViewModel viewModel)
      {
          vm = viewModel;
      }
  
      public void StartProcess()
      {
          bw = new BackgroundWorker();
  
          bw.WorkerReportsProgress = true;
          bw.WorkerSupportsCancellation = true;
          bw.DoWork += new DoWorkEventHandler(ReadFromSource);
          bw.ProgressChanged += new ProgressChangedEventHandler(UpdateProgress_Read);
          bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(Completed_Read);
  
          bw.RunWorkerAsync();
      }
  
      private void ReadFromSource(object sender, DoWorkEventArgs e)
      {
          double records = 0;
          string[] lines = File.ReadAllLines(vm.SourcePath);
          int lineCount = lines.Length;
          double currentLine = 0;
  
          bw.ReportProgress(0, lineCount);
  
          foreach (var line in lines)
          {
              if (line.Length &gt; 0)
              {
                  string syntax = line.Substring(17, 6);
  
                  switch (syntax)
                  {
                      case "$WIMDA":
                          string[] segments = line.Replace(": &lt;- ", ",").Split(',');
                          vm.LineItems.Add(new LineItem()
                          {
                              Time = segments[0],
                              HgPressure = segments[2],
                              BarPressure = segments[4],
                              AirTemp = segments[6],
                              RelHumidity = segments[10],
                              TrueWindDir = segments[14],
                              KnotsWindSpeed = segments[18],
                              MpsWindSpeed = segments[20]
                          });
                          break;
  
                      case "$GPGGA":
                          break;
  
                      default:
                          break;
                  }
              }
              currentLine++;
              bw.ReportProgress(1, currentLine);
          }
          using (StreamWriter writer = new StreamWriter(vm.OutputPath))
          {
              writer.WriteLine($"Time,Pressure(Bar),Pressure(Hg),AirTemp({((vm.ConvertTempSetting) ? "F" : "C")}),RelativeHumidity,TrueWindDirection,WindSpeed(Knots),WindSpeed(M/s)");
              foreach (var lineItem in vm.LineItems)
              {
                  writer.WriteLine($"{lineItem.Time},{lineItem.BarPressure},{lineItem.HgPressure},{((vm.ConvertTempSetting) ? Converters.ConvertFromCelcius(Convert.ToDouble(lineItem.AirTemp)).ToString() : lineItem.AirTemp)},{lineItem.RelHumidity},{lineItem.TrueWindDir},{lineItem.KnotsWindSpeed},{lineItem.MpsWindSpeed}");
                  records++;
              }
          }
          e.Result = records;
      }
  
      private void UpdateProgress_Read(object sender, ProgressChangedEventArgs e)
      {
          vm.IncrementProgress();
          switch (Type.GetTypeCode(e.UserState.GetType()))
          {
              case TypeCode.Double:
                  vm.IncrementProgress();
                  break;
  
              case TypeCode.String:
                  break;
  
              case TypeCode.Int32:
                  vm.AppendStatus(DateTime.Now, $"{(int)e.UserState} lines parsed from log file");
                  break;
  
              default:
                  break;
          }
          if (vm.IsFirst)
          {
              vm.ProgressIsVisible = true;
              vm.IncrementProgress();
              vm.SetMaximum((int)e.UserState);
              vm.IsFirst = false;
          }
      }
      private void Completed_Read(object sender, RunWorkerCompletedEventArgs e)
      {
          if (e.Cancelled)
          {
              vm.AppendStatus(DateTime.Now, $"Conversion was cancelled by user");
          }
          else
          {
              vm.AppendStatus(DateTime.Now, $"{(double)e.Result} records written to {vm.OutputPath}");
          }
          vm.LineItems.Clear();
      }
  }
  </code></pre>
  
  <p>And for my ViewModel:</p>
  
  <pre><code>public class LogDataViewModel : LogDataModel
  {
      #region Commands
      public BeginProcessCommand BeginCommand { get; set; }
      public SelectOutputPathCommand OutputCommand { get; set; }
      public SelectSourceCommand SourceCommand { get; set; }
      public ResetCommand ResetCommand { get; set; }
      #endregion
  
      public bool IsFirst { get; set; }
  
      public LogDataViewModel()
      {
          BeginCommand = new BeginProcessCommand(this);
          OutputCommand = new SelectOutputPathCommand(this);
          SourceCommand = new SelectSourceCommand(this);
          ResetCommand = new ResetCommand(this);
  
          PrepareViewModel();
      }
  
  
      private void PrepareViewModel()
      {
          ProgressValue = 0;
          ProgressMaximum = 0;
          ProgressIsVisible = false;
          IsFirst = true;
  
          OutputPath = Properties.Settings.Default.RememberedSavePath;
          if (LineItems == null) LineItems = new List&lt;LineItem&gt;();
          if (StatusActions == null) StatusActions = new ObservableCollection&lt;StatusAction&gt;();
          AppendStatus(DateTime.Now, "Initialized Program");
      }
  }
  </code></pre>
  
  <p>And lastly, here is the Command:</p>
  
  <pre><code>public class BeginProcessCommand : ICommand
  {
      LogDataViewModel vm;
  
      public BeginProcessCommand(LogDataViewModel viewModel)
      {
          vm = viewModel;
      }
  
      public event EventHandler CanExecuteChanged
      {
          add { CommandManager.RequerySuggested += value; }
          remove { CommandManager.RequerySuggested -= value; }
      }
  
      public bool CanExecute(object parameter)
      {
          bool result = true;
  
          if (!File.Exists(vm.SourcePath))
              result = false;
          try
          {
              if (!Directory.Exists(Path.GetDirectoryName(vm.SourcePath)))
                  result = false;
          }
          catch
          {
              result = false;
          }
          return result;
      }
  
      public void Execute(object parameter)
      {
          ReaderWriter rw = new ReaderWriter(vm);
          rw.StartProcess();
      }
  }
  </code></pre>
  
  <p>Any help at this point is very much appreciated, as I've struggled with this for a while now and any attempt to research solutions yield no help for my particular situation. This seems like a fairly unique scenario, but I am hoping we can make it work.</p>
  
  <p>Thank you!</p>