Thursday, October 15, 2015

Asynchronous ICommand Implementation using TPL (System.Threading.Tasks.Task)

Asynchronous Implementation of ICommand I have implemented Asyn ICommand using Ssytem.Threading.Tasks.Task (i.e TPL). By using this implementation you can execute you command asynchronously. When new execution starts, new object of Task class creates every time and you business logic (which may take long time to complete) runs on that thread. So the main thread (i.e UI thread) gets free so it can be utilize to do other work.

Basically there are four actions which is going to be used to execute task asynchronously.


  • Execute Async Action:Async task which needs to be executed asynchronously.
  • CanExecute Action : which decides whether allow to execute async task or not.
  • OnComplete Action : what to call on successfully completion of aysnc task execution.
  • OnError Action : what to call when any error occurred during execution of async task.



        /// <summary>
        /// delegate which has the pointer of the main method which needs to be executed asynchronously.
        /// </summary>
        private readonly Action<T> _execute;

        /// <summary>
        /// delegate which has the pointer of the main method which needs to be executed asynchronously 
        /// it also provides facility to cancel the exection of async task
        /// </summary>
        private readonly Action<T, CancellationToken> _executeWithCancel;

        /// <summary>
        /// delegate which has the pointer of the method whose output decides whether to allow aysnc method to execute or not
        /// It disables the control to which it is binded (ex. button)
        /// </summary>
        private readonly Func<T, bool> _canExecute;

        /// <summary>
        /// delegate which has the pointer of the method which gets execute when async operation completes.
        /// </summary>
        private readonly Action _onComplete;

        /// <summary>
        /// delegate which has the pointer of the method which gets execute when any error occured during execution of async operation.
        /// the boolean flag value indicates that whether the async operation is cancelled manually or not.
        /// </summary>
        private readonly Action<Exception, bool> _onError;
 


About Task Parallel Library(TPL):

According to the post of TPL written by sacha barber on C# corner : ,
With the help of System.Threading.Tasks.Task we can create new thread and can execute our code asynchronously. The benefit of to use TPL is handles ThreadPool internally. Tasks will be allocated threads by the use of a ThreadPool, which handles the creation of Threads to carry out Tasks, so a lot of the heavy lifting (so to speak) is done behind the scenes for us by TPL.

The other great thing about TPL is that it is aimed at using each core of your CPU, which may have been otherwise idle. It obviously does this using Thread(s) behind the scenes, but you really do not have to get involved with spinning up new Threads at all (OK, in advanced scenarios such as custom Schedulers, maybe, but hey, more on that later; day to day, you really don't have to care about it).


Complete implementation of Aysnc ICommand class:

Copy the following code and paste in your program to use it.


    /// <summary>
    /// using constructor
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class AsyncActionCommand<T> : ICommand
    {
        #region Global Fields
        
        /// <summary>
        /// delegate which has the pointer of the main method which needs to be executed asynchronously.
        /// </summary>
        private readonly Action<T> _execute;

        /// <summary>
        /// delegate which has the pointer of the main method which needs to be executed asynchronously 
        /// it also provides facility to cancel the exection of async task
        /// </summary>
        private readonly Action<T, CancellationToken> _executeWithCancel;

        /// <summary>
        /// delegate which has the pointer of the method whose output decides whether to allow aysnc method to execute or not
        /// It disables the control to which it is binded (ex. button)
        /// </summary>
        private readonly Func<T, bool> _canExecute;

        /// <summary>
        /// delegate which has the pointer of the method which gets execute when async operation completes.
        /// </summary>
        private readonly Action _onComplete;

        /// <summary>
        /// delegate which has the pointer of the method which gets execute when any error occured during execution of async operation.
        /// the boolean flag value indicates that whether the async operation is cancelled manually or not.
        /// </summary>
        private readonly Action<Exception, bool> _onError;

        /// <summary>
        /// An instance of the <see cref="Task"/> class which contains the ref of the async operation thread.
        /// </summary>
        private Task _currentExecutingTask;

        /// <summary>
        /// Its a cancellation token to cancel the async operation when required.
        /// </summary>
        private CancellationTokenSource _taskCancellationTokenSource;

        #endregion

        #region Constructors
        
        /// <summary>
        /// Initialize the new instance of then AsyncDelegateCommand class
        /// </summary>
        /// <param name="executeMethod"> async Execute method delegate ref</param>
        /// <param name="canExecuteMethod"> canExecute method delegate ref</param>
        /// <param name="onComplete"> completion method delegate ref</param>
        /// <param name="onError"> error method delegate ref</param>
        public AsyncActionCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod = null,
                                     Action onComplete = null, Action<Exception, bool> onError = null)
        {
            _execute = executeMethod;
            _canExecute = canExecuteMethod;
            _onComplete = onComplete;
            _onError = onError;
        }

        /// <summary>
        /// Initialize the new instance of then AsyncDelegateCommand class
        /// </summary>
        /// <param name="executeMethod"> async Execute method delegate ref with cancellation token</param>
        /// <param name="canExecuteMethod"> canExecute method delegate ref</param>
        /// <param name="onComplete"> completion method delegate ref</param>
        /// <param name="onError"> error method delegate ref</param>
        public AsyncActionCommand(Action<T, CancellationToken> executeMethod, Func<T, bool> canExecuteMethod = null,
                                    Action onComplete = null, Action<Exception, bool> onError = null)
        {
            _executeWithCancel = executeMethod;
            _canExecute = canExecuteMethod;
            _onComplete = onComplete;
            _onError = onError;
        }

        #endregion

        #region Properties & Events
        
        /// <summary>
        /// Gets boolean value which indicates whether async task is being executing or not.
        /// </summary>
        public bool IsBusy
        {
            get
            {
                if (_currentExecutingTask == null)
                    return false;

                var isbusy = _currentExecutingTask.IsCompleted || _currentExecutingTask.IsCanceled || _currentExecutingTask.IsFaulted;
                return !isbusy;
            }
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        #endregion

        #region Public methods
        
        /// <summary>
        /// Executes long running aysn task.
        /// </summary>
        /// <param name="parameter">async task parameter</param>
        public void Execute(object parameter)
        {
            if (_execute != null)
            {
                _currentExecutingTask = Task.Factory.StartNew(() =>
                {
                    _execute((T)parameter);
                }).ContinueWith(task =>
                {
                    TaskCallback(_onComplete, _onError, task);
                }, TaskScheduler.FromCurrentSynchronizationContext()).ContinueWith(task => task.Dispose());
            }
            else
            {
                _taskCancellationTokenSource = new CancellationTokenSource();
                var token = _taskCancellationTokenSource.Token;

                _currentExecutingTask = Task.Factory.StartNew(new Action<object>((argument) =>
                {
                    _executeWithCancel((T)argument, token);
                }), parameter, token).ContinueWith(task =>
                {
                    TaskCallback(_onComplete, _onError, task);
                }, TaskScheduler.FromCurrentSynchronizationContext()).ContinueWith(task => task.Dispose());
            }
        }

        /// <summary>
        /// Decides whethe allow to execute async operation or not.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns>Retruns true or false</returns>
        public bool CanExecute(object obj)
        {
            var status = true;

            if (_currentExecutingTask != null)
                status = _currentExecutingTask.IsCompleted || _currentExecutingTask.IsCanceled || _currentExecutingTask.IsFaulted;

            if (_canExecute != null && status)
                status = _canExecute((T)obj);

            return status;
        }

        /// <summary>
        /// Cancels the running async task 
        /// </summary>
        public void Cancel()
        {
            if (IsBusy && _taskCancellationTokenSource != null)
                _taskCancellationTokenSource.Cancel();
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Its get execute when aysnc tasks get completed or any error occurred or cancelled manually
        /// </summary>
        /// <param name="onComplete">contains ref of competition method</param>
        /// <param name="onError">contains ref of error method</param>
        /// <param name="task">Async task ref</param>
        static void TaskCallback(Action onComplete, Action<Exception, bool> onError, Task task)
        {
            switch (task.Status)
            {
                case TaskStatus.Canceled: // executes when task is cancelled manually
                    if (onError != null)
                    {
                        var ex = new AggregateException(new Exception("Task has been canceled"));
                        onError(ex, true);
                    }
                    break;
                case TaskStatus.Faulted: // executes when erro occured during execution
                    if (onError != null) onError(task.Exception, false);
                    break;
                case TaskStatus.RanToCompletion: // executes when async task completed.
                    if (onComplete != null) onComplete();
                    break;
                default:
                    break;
            }
        }

        #endregion
    }


How to use the class : Example


public class MyClass
    {
        public ICommand AsyncCommand
        {
            get
            {
                return new AsyncDelegateCommand<object>(AsyncMethod, CanExecuteAsyncCommand, RunOnCompletion, RunOnError);
            }
        }

        private void AsyncMethod(object obj, CancellationToken token)
        {
            //Write logic here which is going be executed asynchronosuly
        }

        private void RunOnError(Exception arg1, bool arg2)
        {
           //Write logic here to catch the error if thrown from the async method execution.
        }

        private void RunOnCompletion()
        {
           //Write your logic to which want to execute after successfully completion execution of async method
        }

        private bool CanExecuteAsyncCommand(object obj)
        {
            return true; // write your code here to decide whether to execute aysn method or not.
        }
    }

Improvement is appreciated always

Thank You


1 comment :