Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Cinchoo - Abortable Long Running Async Task

5.00/5 (4 votes)
30 May 2016CPOL3 min read 18.4K   96  
Tip: Asynchronous abortable long running tasks using Cinchoo framework

Introduction

In .NET, there are a number of ways in which you can start asynchronous long running tasks by using Thread / Task / ThreadPool. Every approach has pros and cons to it. Please refer to MSDN for more information about them. With the TPL, you are exposed to Cancellation model whereby you can gracefully stop your long running task. In this article, I'm going to elaborate about a way to start abortable long running task asynchronously using Cinchoo framework and talk about the way to abort it in a forceful manner.

First, each Windows application has a single main thread it uses to render the window / execute main task, process events and execute custom code. In a single threaded environment, any long running tasks will block the main thread. In particular, it will block the main UI thread and the application becomes unresponsive in Windows application.

Cinchoo simplifies the model to execute a long running task asynchronously and gives control to the caller to abort it anytime. Besides the standard behavior, it offers few useful features like number of retries in case of failure, timeout period via the API. Alright, let's cut to the chase on to how to use them using the code.

Here is the summary of features:

  • Abort the task anytime via Abort() method
  • Can specify the timeout period to run the task
  • Auto retry in case of the task failure
  • Make decision to continue the retry via callback machanism

Download the Latest Cinchoo Binary here. (Nuget Command: Install-Package Cinchoo)

Using the Code

The API is exposed via ChoActionEx class. The signature of the API looks like below:

C#
public static class ChoActionEx
{
    public static ChoAbortableAsyncResult RunAsync
	(this Action action, ChoAbortableAsyncCallback callback = null, object state = null, 
	int timeout = -1, int maxNoOfRetry = 0, int sleepBetweenRetry = 5000);
}

Where:

  • action - A long running task method
  • callback - References a method to be called when a corresponding asynchronous operation completes
  • state - A user-defined object that qualifies or contains information about an asynchronous operation
  • timeout - A time-out value in milliseconds. -1, infinite
  • maxNoOfRetry - Maximum number of retry attempts made in case of failure
  • sleepBetweenRetry - Number of milliseconds to sleep between retry. Default is 5000 ms

This API method returns ChoAbortableAsyncResult object. This encapsulates the results of an asynchronous operation on a delegate. It exposes the below members:

  • AsyncState - Gets the object provided in the 'state' parameter of a RunAsync method call
  • AsyncWaitHandle - Gets a WaitHandle that encapsulates Win32 synchronization handles, and allows the implementation of various synchronization schemes
  • CompletedSynchronously - Gets a value indicating whether the RunAsync call completed synchronously
  • IsAborted - Gets a value indicating whether the server has aborted the call
  • IsCompleted - Gets a value indicating whether the server has completed the call
  • IsRetryAttempt - Gets a value indicating whether the server has retried the call
  • IsTimeout - Gets a value indicating whether the method has completed with the timeout
  • Result - Gets the result value from the async call if any
  • RetryCount - Gets a value of the number of retry attempts made by the server
  • Exception - Gets any exception captured during task execution
  • CanContinue - Gets or Sets where to continue the retry
  • EndInvoke() - Retrieves the return value of the asynchronous operation. If the asynchronous operation has not been completed, this function will block until the result is available
  • Abort() - Aborts the currently executing asynchrounous call

Sample #1

The below sample shows how to execute a method asynchrounously, wait for it to complete.

C#
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Main thread: {0}", Thread.CurrentThread.ManagedThreadId);
        Console.WriteLine();
 
        //Run method async, wait for it to complete
        Console.WriteLine("TEST #1: Run method, wait for it to complete...");
        ChoAbortableAsyncResult r = ChoActionEx.RunAsync(LongRunningTask);
        Console.WriteLine("Waiting for worker thread to complete.");
        r.EndInvoke();
        Console.WriteLine();
    }
 
    private static void LongRunningTask()
    {
        Console.WriteLine("Starting task... (Sleeping for 10 secs)");
        Console.WriteLine("Worker thread: {0}", Thread.CurrentThread.ManagedThreadId);
        Thread.Sleep(10 * 1000);
        Console.WriteLine("Task completed.");
    }
}

Sample #2

The below sample shows how to execute a method asynchrounously, abort it after 5 secs.

C#
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Main thread: {0}", Thread.CurrentThread.ManagedThreadId);
        Console.WriteLine();

        //Run method async, abort it after 5 secs
        Console.WriteLine("TEST #2: Run method, abort after 5 secs...");
        ChoAbortableAsyncResult r1 = ChoActionEx.RunAsync(LongRunningTask);
        Console.WriteLine("Waiting for 5 secs...");
        Thread.Sleep(5000);
        Console.WriteLine("Aborting working thread.");
        r1.Abort();
        Console.WriteLine();
    }
 
    private static void LongRunningTask()
    {
        Console.WriteLine("Starting task... (Sleeping for 10 secs)");
        Console.WriteLine("Worker thread: {0}", Thread.CurrentThread.ManagedThreadId);
        Thread.Sleep(10 * 1000);
        Console.WriteLine("Task completed.");
    }
}

Sample #3

The below sample shows how to execute a method asynchrounously, timeout after 5 secs.

C#
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Main thread: {0}", Thread.CurrentThread.ManagedThreadId);
        Console.WriteLine();

        //Run method async, with timeout 5 secs
        Console.WriteLine("TEST #3: Run method with 5 secs timeout...");
        ChoAbortableAsyncResult r2 = ChoActionEx.RunAsync(LongRunningTask, null, null, 5000);
        Console.WriteLine("Waiting for worker thread to complete or timeout.");
        try
        {
            r2.EndInvoke();
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
        Console.WriteLine();
    }
 
    private static void LongRunningTask()
    {
        Console.WriteLine("Starting task... (Sleeping for 10 secs)");
        Console.WriteLine("Worker thread: {0}", Thread.CurrentThread.ManagedThreadId);
        Thread.Sleep(10 * 1000);
        Console.WriteLine("Task completed.");
    }
}

Sample #4

The below sample shows how to execute a method asynchrounously, retry couple of attempts.

C#
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Main thread: {0}", Thread.CurrentThread.ManagedThreadId);
        Console.WriteLine();

        //Run a exception thrown method async 
        Console.WriteLine("TEST #4: Run method with 2 retries...");
        ChoAbortableAsyncResult r3 = 
		ChoActionEx.RunAsync(LongRunningTaskWithException, null, null, -1, 2, 5000);
        Console.WriteLine("Waiting for worker thread to complete.");
        try
        {
            r3.EndInvoke();
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
        Console.WriteLine();
    }
    
    private static void LongRunningTaskWithException()
    {
        Console.WriteLine("Starting task... (Sleeping for 10 secs)");
        Console.WriteLine("Worker thread: {0}", Thread.CurrentThread.ManagedThreadId);
        Thread.Sleep(10 * 1000);
        throw new ApplicationException("Test task exception.");
    }
}

Sample #5

The below sample shows how to execute a method asynchrounously, cancel it in the callback.

C#
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Main thread: {0}", Thread.CurrentThread.ManagedThreadId);
        Console.WriteLine();

        Console.WriteLine("TEST #5: Run method with 2 retries, but cancel the retry on callback...");
        ChoAbortableAsyncResult r5 = ChoActionEx.RunAsync(LongRunningTaskWithException, (t) =>
        {
            ChoAbortableAsyncResult t1 = t as ChoAbortableAsyncResult;
            Console.WriteLine("Canceling the task...");
            if (t1.Exception != null)
                t1.CanContinue = false;
        }, null, -1, 2, 5000);
        try
        {
            r5.EndInvoke();
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
        Console.WriteLine();
    }
    
    private static void LongRunningTaskWithException()
    {
        Console.WriteLine("Starting task... (Sleeping for 10 secs)");
        Console.WriteLine("Worker thread: {0}", Thread.CurrentThread.ManagedThreadId);
        Thread.Sleep(10 * 1000);
        throw new ApplicationException("Test task exception.");
    }
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)