Cinchoo - Abortable Long Running Async Task
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:
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 methodcallback
- References a method to be called when a corresponding asynchronous operation completesstate
- A user-defined object that qualifies or contains information about an asynchronous operationtimeout
- A time-out value in milliseconds. -1, infinitemaxNoOfRetry
- Maximum number of retry attempts made in case of failuresleepBetweenRetry
- 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 aRunAsync
method callAsyncWaitHandle
- Gets aWaitHandle
that encapsulates Win32 synchronization handles, and allows the implementation of various synchronization schemesCompletedSynchronously
- Gets a value indicating whether the RunAsync call completed synchronouslyIsAborted
- Gets a value indicating whether the server has aborted the callIsCompleted
- Gets a value indicating whether the server has completed the callIsRetryAttempt
- Gets a value indicating whether the server has retried the callIsTimeout
- Gets a value indicating whether the method has completed with the timeoutResult
- Gets the result value from theasync
call if anyRetryCount
- Gets a value of the number of retry attempts made by the serverException
- Gets any exception captured during task executionCanContinue
- Gets or Sets where to continue the retryEndInvoke()
- Retrieves the return value of the asynchronous operation. If the asynchronous operation has not been completed, this function will block until the result is availableAbort()
- Aborts the currently executing asynchrounous call
Sample #1
The below sample shows how to execute a method asynchrounously, wait for it to complete.
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.
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.
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.
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.
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.");
}
}