Delen via


Weekend Fun 02: Retryable Tasks

For the fun of it. Let us assume you need a retry logic. Similar to the one that can handle cloud transient failures (more here: https://www.asp.net/aspnet/overview/developing-apps-with-windows-azure/building-real-world-cloud-apps-with-windows-azure/transient-fault-handling) but what of more general. Logic can be briefly described

Try N number of times to do X, if at any point you encountered exception A or B or C fail and don’t retry

On a side note: I needed the below as a JS function for a something I am doing based on nodejs but turned out to be harder than expected due to the way lexical scope is transferred from one call back to another.

Ultimately speaking you need it to look like that:

      RetryableTask<int> rt = new RetryableTask<int>(); // this task will return an int when it finally successed
            rt.RertyCount = 1000; // try for a 1000 time
            rt.FailIF = (exp) => // Stop if you encounter one of these exceptions
            {
                // handle and filter error here
                return false;

            };
            rt.FuncToExecute = async (cts) => // this is what we will execute 
            {
                Debug.WriteLine("FuncToExecute - Thread:" + System.Threading.Thread.CurrentThread.ManagedThreadId);
                return 0;
            };
  

    Then we are ready to execute our task

 var nRes = rt.Execute();
        // do a lot of things here 
        
        await nRes // this will return if when we get results 
 
// if the task failed (i.e. encountered an irrecoverable failure we will get one of the following exceptions 
// TaskExecutionException as in we tried 1000s times and no results
// any other exception as thrown (one that FailIf returned true on).

 

the class also supports cancelation by calling rt.Cancel() and support chain-ing the task by setting the cancelation token source field before calling execute.

The complete code is below

 

     public class RetryableTask<T>
    {
        public Func<CancellationTokenSource, Task<T>> FuncToExecute = null;
        public Func<Exception, bool> FailIF = new Func<Exception, bool>((e) => { return false; }); // basically always retry
        public CancellationTokenSource cts = new CancellationTokenSource();
        public int RertyCount = 1;
        public int DelayMiliSec = 0;
     


        protected void Validate()
        {
           if (null == this.FuncToExecute)
                throw new ArgumentNullException("FuncToExecute");

            if (null == this.cts)
                throw new ArgumentNullException("cts");

            if (null == this.FailIF)
                throw new ArgumentNullException("FailIF");
        }

        
        public async Task<T> Execute()
        {

            Validate();
            Debug.WriteLine("Do/While - Thread:" + System.Threading.Thread.CurrentThread.ManagedThreadId);


            int nCurrent = 1;
            do
            {

                if (cts.IsCancellationRequested)
                {
                    Debug.WriteLine("Task canceled.. breaking");
                    break;
                }

                try
                {
                    return await FuncToExecute(cts);
                }
                catch (Exception e)
                {
                    if (true == this.FailIF(e)) // fail if delegate should handle and log the error as needed
                        throw;
                }

                nCurrent++;


                // you don't want to wait unncessary
                if (cts.IsCancellationRequested)
                {
                    Debug.WriteLine("Task canceled.. breaking");
                    break;
                }

                if (this.DelayMiliSec > 0)
                    await Task.Delay(this.DelayMiliSec, cts.Token);

            } while (nCurrent <= RertyCount);

           // if we are here then task failed to execute given the # of retries
            throw new TaskExecutionFailedException(nCurrent);


        }
        public async Task Cancel()
        {
              if (null == this.cts)
                throw new ArgumentNullException("cts");

              this.cts.Cancel();
              await Task.FromResult(0);
             
        }
    }

 

The complete code to run the above in a console app is below:

 RetryableTask<int> rt = new RetryableTask<int>(); // this task will return an int when it finally successed
            rt.RertyCount = 1000; // try for a 1000 time
            rt.FailIF = (exp) => // Stop if you incounter these specic exceptions
            {
                // handle and filter error here
                return false;

            };
            rt.FuncToExecute = async (cts) => // this is what we will execute 
            {
                Debug.WriteLine("FuncToExecute - Thread:" + System.Threading.Thread.CurrentThread.ManagedThreadId);
                return 0;
            };

            int nResults;
            Task t = Task.Run(async () => { nResults = await rt.Execute(); });

            Task tCancel = Task.Run(async () =>
            {
                await Task.Delay(10000);
                Debug.WriteLine("About to Cancel");
                await rt.Cancel();
            });

            Console.WriteLine("running");
            t.Wait();




            Console.WriteLine("Done!");

            Console.Read();

 

 

Have a good weekend!