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 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.
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Main thread: {0}", Thread.CurrentThread.ManagedThreadId);
Console.WriteLine();
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();
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();
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();
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.");
}
}