Wednesday, December 30, 2009

Reporting Progress status using Asynchronous / BackgroundWorker


Well, this is an example to show how to create an Asynchronous methods / calls conforming to the Event-based Asynchronous pattern. This example demonstrate how to use BackgroundWorker and Asynchronous methods to report the progress status of the task you are performing.

File 1: Entity.cs

using System;
using System.ComponentModel;
using System.Threading;
using System.Runtime.Remoting.Messaging;

namespace BackgroundWorkerClient
{
 internal class Entity
 {
  #region Private Declarations
  private int _fileCount;
  private int _folderCount;
  private int _progress;
  private ProgressStatus _status = ProgressStatus.Idle;
  private bool _taskRunning;
  private readonly object _sync = new object();
  private AsyncContext _taskContext;

  #endregion

  #region Public Properties
  public bool IsBusy { get { return _taskRunning; } }
  #endregion

  #region EventHandlers & Delegates
  public event AsyncCompletedEventHandler OnTaskCompleted;
  public event TaskProgressChangedEventHandler OnTaskProgressChanged;

  private delegate void TaskWorkerDelegate(string path, AsyncOperation asyncOperation, AsyncContext asyncContext, out bool cancelled);
  #endregion

  #region Protected Virtual Methods
  protected virtual void DoOnTaskCompleted(AsyncCompletedEventArgs e)
  {
   if (OnTaskCompleted != null)
    OnTaskCompleted(this, e);
  }
  protected virtual void DoOnTaskProgressChanged(TaskProgressChangedEventArgs eventArgs)
  {
   if (OnTaskProgressChanged != null)
    OnTaskProgressChanged(this, eventArgs);
  }
  #endregion

  #region Asynchronous Operations

  private void StartProcessing(string path, AsyncOperation asyncOperation, AsyncContext asyncContext, out bool cancelled)
  {
   cancelled = false;
   _status = ProgressStatus.Started;
   _fileCount = 0;
   _folderCount = 0;
   _progress = 0;
   for (int folderCount = 0; folderCount < 100; folderCount++)
   {
    // if (_progress >= 100) _progress = 0;

    // compute progress
    // int _progress = 100 * (i + 1) / SelectedFiles.Length;

    _progress = 100 * (folderCount + 1) / 100;
    _status = ProgressStatus.ProcessingFolders;
    _folderCount = folderCount;
    path = string.Format("Project{0}", _folderCount);
    asyncOperation.Post(e => DoOnTaskProgressChanged((TaskProgressChangedEventArgs)e), new TaskProgressChangedEventArgs(_status, path, _progress, null));
    Thread.Sleep(100);
    for (int fileCount = 0; fileCount < 100; fileCount++)
    {
     _status = ProgressStatus.ProcessingFiles;
     _fileCount = fileCount;
     path = string.Format("Project{0}, Class{1}.cs", _folderCount, _fileCount);
     asyncOperation.Post(e => DoOnTaskProgressChanged((TaskProgressChangedEventArgs)e), new TaskProgressChangedEventArgs(_status, path, _progress, null));

     if (asyncContext.IsCancelling)
     {
      cancelled = true;
      _status = ProgressStatus.Stopped;
      return;
     }
     Thread.Sleep(10);
    }

    if (asyncContext.IsCancelling)
    {
     cancelled = true;
     _status = ProgressStatus.Stopped;
     return;
    }
   }
   _status = ProgressStatus.Completed;
  }
  private void TaskCompletedCallback(IAsyncResult asyncResult)
  {
   // Retrieve the delegate.
   AsyncResult result = (AsyncResult)asyncResult;
   // get the original worker delegate and the AsyncOperation instance
   TaskWorkerDelegate worker = (TaskWorkerDelegate)result.AsyncDelegate;
   AsyncOperation async = (AsyncOperation)asyncResult.AsyncState;
   bool cancelled;

   // finish the asynchronous operation
   worker.EndInvoke(out cancelled, asyncResult);

   // clear the running task flag
   lock (_sync)
   {
    _taskRunning = false;
    _taskContext = null;
   }

   // raise the completed event
   AsyncCompletedEventArgs completedArgs = new AsyncCompletedEventArgs(null, cancelled, null);
   async.PostOperationCompleted(e => DoOnTaskCompleted((AsyncCompletedEventArgs)e), completedArgs);
  }
  #endregion

  #region Public Methods
  public void StartAsync(string path)
  {
   TaskWorkerDelegate worker = StartProcessing;
   AsyncCallback completedCallback = TaskCompletedCallback;

   lock (_sync)
   {
    if (_taskRunning)
     throw new InvalidOperationException("The control is currently busy.");

    AsyncOperation async = AsyncOperationManager.CreateOperation(null);
    AsyncContext asyncContext = new AsyncContext();
    bool cancelled;
    worker.BeginInvoke(path, async, asyncContext, out cancelled, completedCallback, async);
    _taskRunning = true;
    _taskContext = asyncContext;
   }
  }
  public void CancelAsync()
  {
   lock (_sync)
   {
    if (_taskContext != null)
     _taskContext.Cancel();
   }
  }
  #endregion

 }

 internal class AsyncContext
 {
  private readonly object _sync = new object();
  private bool _isCancelling;

  public bool IsCancelling
  {
   get
   {
    lock (_sync) { return _isCancelling; }
   }
  }

  public void Cancel()
  {
   lock (_sync) { _isCancelling = true; }
  }
 }

 public enum ProgressStatus
 {
  Idle,
  Started,
  Stopped,
  Completed,
  ProcessingFiles,
  ProcessingFolders
 }

 public class TaskProgressChangedEventArgs : ProgressChangedEventArgs
 {
  public string FileName { get; private set; }
  public ProgressStatus Status { get; private set; }
  public TaskProgressChangedEventArgs(ProgressStatus status, string fileName, int progressPercent, object userState)
   : base(progressPercent, userState)
  {
   FileName = fileName;
   Status = status;
  }
 }

 public delegate void TaskProgressChangedEventHandler(object sender, TaskProgressChangedEventArgs e);
}
File 2: ProgressController.cs
using System.ComponentModel;

namespace BackgroundWorkerClient
{
 public class ProgressController
 {
  private Entity _entity;
  public event AsyncCompletedEventHandler OnCompleted;
  public event TaskProgressChangedEventHandler OnProgressChanged;

  public ProgressController()
  {
   Initialize();
  }

  private void Initialize()
  {
   _entity = new Entity();
   _entity.OnTaskCompleted += DoOnCompleted;
   _entity.OnTaskProgressChanged += DoOnProgressChanged;
  }

  public void Start(string path)
  {
   _entity.StartAsync(path);
  }

  public void Stop()
  {
   _entity.CancelAsync();
  }


  void DoOnProgressChanged(object sender, TaskProgressChangedEventArgs progressArgs)
  {
   if (OnProgressChanged != null)
    OnProgressChanged(this, progressArgs);
  }

  void DoOnCompleted(object sender, AsyncCompletedEventArgs completedArgs)
  {
   if (OnCompleted != null)
   {
    OnCompleted(this, completedArgs);  
   }
  }

 }
}

File 3: AsyncClient.cs
using System;
using System.Threading;
using System.ComponentModel;
using System.Windows.Forms;
using System.Diagnostics;

namespace BackgroundWorkerClient
{
 public class AsyncClient : Form
 {
  Label _lblStatus;
  Button _btnStart;
  Button _btnCancel;
  ProgressBar _progressBar;
  BackgroundWorker _backgroundWorker;
  CheckBox _ckUseController;
  ProgressController _controller;

  public AsyncClient()
  {
   InitializeComponent();
   InitializeController();
   SetButtons(true);
  }

  private void InitializeController()
  {
   _controller = new ProgressController();
   _controller.OnCompleted += OnControllerTaskCompleted;
   _controller.OnProgressChanged += OnControllerProgressChanged;
  }

  #region Windows Form Designer generated code
  void InitializeComponent()
  {
   this._lblStatus = new System.Windows.Forms.Label();
   this._progressBar = new System.Windows.Forms.ProgressBar();
   this._btnCancel = new System.Windows.Forms.Button();
   this._btnStart = new System.Windows.Forms.Button();
   this._backgroundWorker = new System.ComponentModel.BackgroundWorker();
   this._ckUseController = new System.Windows.Forms.CheckBox();
   this.SuspendLayout();
   // 
   // _lblStatus
   // 
   this._lblStatus.AutoSize = true;
   this._lblStatus.Location = new System.Drawing.Point(12, 25);
   this._lblStatus.Name = "_lblStatus";
   this._lblStatus.Size = new System.Drawing.Size(101, 13);
   this._lblStatus.TabIndex = 0;
   this._lblStatus.Text = "Status: Not Started";
   // 
   // _progressBar
   // 
   this._progressBar.Location = new System.Drawing.Point(12, 43);
   this._progressBar.MarqueeAnimationSpeed = 50;
   this._progressBar.Name = "_progressBar";
   this._progressBar.Size = new System.Drawing.Size(337, 21);
   this._progressBar.Style = System.Windows.Forms.ProgressBarStyle.Continuous;
   this._progressBar.TabIndex = 1;
   // 
   // _btnCancel
   // 
   this._btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
   this._btnCancel.Enabled = false;
   this._btnCancel.Location = new System.Drawing.Point(274, 82);
   this._btnCancel.Name = "_btnCancel";
   this._btnCancel.Size = new System.Drawing.Size(75, 23);
   this._btnCancel.TabIndex = 2;
   this._btnCancel.Text = "&Cancel";
   this._btnCancel.Click += new System.EventHandler(this.OnCancel);
   // 
   // _btnStart
   // 
   this._btnStart.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
   this._btnStart.Location = new System.Drawing.Point(183, 82);
   this._btnStart.Name = "_btnStart";
   this._btnStart.Size = new System.Drawing.Size(75, 23);
   this._btnStart.TabIndex = 3;
   this._btnStart.Text = "&Start";
   this._btnStart.Click += new System.EventHandler(this.OnStart);
   // 
   // _backgroundWorker
   // 
   this._backgroundWorker.WorkerReportsProgress = true;
   this._backgroundWorker.WorkerSupportsCancellation = true;
   this._backgroundWorker.DoWork += new System.ComponentModel.DoWorkEventHandler(this.OnDoWork);
   this._backgroundWorker.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.OnCompleted);
   this._backgroundWorker.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(this.OnProgressChanged);
   // 
   // _ckUseController
   // 
   this._ckUseController.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
   this._ckUseController.AutoSize = true;
   this._ckUseController.Location = new System.Drawing.Point(15, 86);
   this._ckUseController.Name = "_ckUseController";
   this._ckUseController.Size = new System.Drawing.Size(141, 17);
   this._ckUseController.TabIndex = 4;
   this._ckUseController.Text = "&Process using Controller";
   this._ckUseController.UseVisualStyleBackColor = true;
   // 
   // AsyncClient
   // 
   this.ClientSize = new System.Drawing.Size(361, 117);
   this.Controls.Add(this._ckUseController);
   this.Controls.Add(this._lblStatus);
   this.Controls.Add(this._btnStart);
   this.Controls.Add(this._btnCancel);
   this.Controls.Add(this._progressBar);
   this.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
   this.MaximizeBox = false;
   this.MinimizeBox = false;
   this.Name = "AsyncClient";
   this.ShowInTaskbar = false;
   this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
   this.Text = "Async Client";
   this.ResumeLayout(false);
   this.PerformLayout();

  }
  #endregion

  [STAThread]
  static void Main()
  {
   Application.EnableVisualStyles();
   Application.Run(new AsyncClient());
  }

  void OnDoWork(object sender, DoWorkEventArgs doWorkArgs)
  {
   Debug.Assert(doWorkArgs.Argument != null);
   BackgroundWorker backgroundWorker = sender as BackgroundWorker;
   Debug.Assert(backgroundWorker != null);

   int count = (int)doWorkArgs.Argument;

   doWorkArgs.Result = null;

   for (int progress = 0; progress <= count; progress += count / 10)
   {
    if (backgroundWorker.CancellationPending)
    {
     doWorkArgs.Cancel = true;
     break;
    }
    Thread.Sleep(500);

    backgroundWorker.ReportProgress(progress);
   }
  }

  void OnProgressChanged(object sender, ProgressChangedEventArgs progressArgs)
  {
   _progressBar.Value = progressArgs.ProgressPercentage;
  }

  void OnCompleted(object sender, RunWorkerCompletedEventArgs completedEventArgs)
  {
   _lblStatus.Text = completedEventArgs.Cancelled ? "Process: Cancelled" : "Process: Completed";
   SetButtons(true);
  }

  void OnControllerProgressChanged(object sender, TaskProgressChangedEventArgs progressArgs)
  {
   _lblStatus.Text = string.Format("{0} : {1}", progressArgs.Status, progressArgs.FileName);
   _progressBar.Value = progressArgs.ProgressPercentage;
  }

  void OnControllerTaskCompleted(object sender, AsyncCompletedEventArgs completedArgs)
  {
   _lblStatus.Text = completedArgs.Cancelled ? "Process: Cancelled" : "Process: Completed";
   SetButtons(true);
  }

  private void SetButtons(bool isCompleted)
  {
   _btnCancel.Enabled = !isCompleted;
   _btnStart.Enabled = isCompleted;
   _ckUseController.Enabled = isCompleted;
  }

  public void TraceThread()
  {
   Trace.WriteLine(Thread.CurrentThread.ManagedThreadId);
  }

  void OnCancel(object sender, EventArgs e)
  {
   if (_ckUseController.Checked)
    _controller.Stop();
   else
    _backgroundWorker.CancelAsync();

   SetButtons(true);   
  }

  void OnStart(object sender, EventArgs e)
  {
   if (_ckUseController.Checked)
    _controller.Start("empty");
   else
    _backgroundWorker.RunWorkerAsync(100);

   _lblStatus.Text = "Status: In Progress";
   SetButtons(false);
  }

 }
}

Some of the useful links from MSDN:

1 comment:

Anonymous said...

Congratz...

You really did a good job here.

I got your example to make something alike

but in WCF tech.

Thanks.