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:

Thursday, December 10, 2009

Difference between IServiceBehavior and IEndpointBehavior

I believe this could sound silly, but for those who are fairly new to WCF definitely would like know. By looking at the interface names, we can say that one is for extending the "Service" and the other is for extending "Endpoints". We have recently done couple of interesting things in our organization and thought would share this information with you guys.

The ApplyDispatchBehavior method on IServiceBehavior has access to all endpoints and their runtime components and so shouldn't that be enough for wiring up customization? The short answer is yes, but there are subtle differences.

Some usability differences are:
  • ServiceBehavior applies only on a Service while EndpointBehavior applies on both client and service.

  • ServiceBehavior can be specified via Attributes/Code/Config file while Endpointbehavior can be specified through Code or Config file.

  • ServiceBehavior has access to all ServiceEndpoints dispatch runtime and so could modify all dispatch runtimes while EndpointBehavior gets called with the runtime for that endpoint only.


Look at it this way, ServiceBehavior lets you access runtime parameters for all endpoints while EndpointBehavior lets you access runtime components only for that endpoint. So if you have a need to extend functionality that spawns the entire contract (or multiple contracts) then use ServiceBehavior and if you are interested in extending one specific endpoint then use EndpointBehavior.

And of course, the biggest difference is if you want to customize endpoints on client/consumer then the only option is IEndpointBehavior.

Thursday, August 06, 2009

Get Ready for Delphi RAD Studio 2010

New features include:
  • IDE Insight – a timesaving tool to easily find files, components, features and settings using simple keystrokes and search terms

  • Code Formatter – to implement consistent coding styles with minimal effort

  • Class Explorer – for a configurable hierarchical view of class libraries throughout a project and enabling fast navigation to declarations and implementations and now available for C++Builder

  • Data Visualizers – that make debugging easier by displaying visual representations of data in definable forms

  • Debugger Thread Control – to freeze, thaw and isolate individual threads within applications during debugging to track down problems faster