TPL is a new library introduced in C# version 4.0 to provide good control over threads, to allow use of multi-core CPUs using the parallel execution of threads. The following discussion is not about TPL but it's about the ContinueWith function available in the Task Class of the TPL and the await keyword introduced in C# 5.0 to support asynchronous calls.
ContinueWith
The ContinueWith function is a method available on the task that allows executing code after the task has finished execution. In simple words it allows continuation.
Things to note here is that ContinueWith also returns one Task. That means you can attach ContinueWith one task returned by this method.
Example
ContinueWith
The ContinueWith function is a method available on the task that allows executing code after the task has finished execution. In simple words it allows continuation.
Things to note here is that ContinueWith also returns one Task. That means you can attach ContinueWith one task returned by this method.
Example
- public void ContinueWithOperation()
- {
- Task<string> t = Task.Run(() => LongRunningOperation("Continuewith", 500));
- t.ContinueWith((t1) =>
- {
- Console.WriteLine(t1.Result);
- });
- }
The ContinueWith operation is executed by the default thread scheduler. One can also provide an other scheduler for running the task on it, this is discussed later in this article.
Note: The following code is LongRunningOperation called by the task. LongRunningOpertion here is just an example in a real program. One cannot call a long-running operation on a task and if one wants to call longrunning task then one must pass TaskCreationOperation.LongRunning.
- private string LongRunningOperation(string s, int sec)
- {
- Thread.Sleep(sec);
- return s + " Completed";
- }
The await keyword causes the runtime to run the operation on a new task and causes the executing thread to return and continue with an execution. (In most cases it executes the main thread of the application). Once the await operation finishes it returns to the statement where it left off (in other words it returns to the caller, in other words it returns depending on the state saved) and starts executing statements.
So await waits for a new task to finish and ensures the continuation once the execution of the waiting task is finished.
The await keyword is used with async to do asynchronous programming in C#. It's called asynchronous programming because the runtime captures the state of the program when it encounters the await keyword (that is similar to yield in an iterator) and restores the state back once the waited task finishes so the continuation runs in the correct context.
Example
- public async void AsyncOperation()
- {
- string t = await Task.Run(() => LongRunningOperation("AsyncOperation", 1000));
- Console.WriteLine(t);
- }
So here because the state is saved when await encountered flow returns on the same context one operation on the task is executed.
Note: State has the detail about executioncontext/synchronizationcontext.
So from the preceding it's clear that both Task.ContinueWith and await Task wait for the task to finish and allows continuation after the task completion. But they work differently.
Difference between ContinueWith and await:
- Saving Sate for and return of the execution context
ContinueWith doesn't save any kind of state, the continuation operation is attached using ContinueWith run on the default thread scheduler in case a scheduler is not provided.
await: when encountering this keyword the state is saved and once the task on which await is done completes its execution the flow picks up the saved state data and starts the execution statement after await. (Note: State is having detail about executioncontext/synchronizationcontext.). - Posting Completed Task result on UI Control
The following is an example with ContinueWith and await to display the completion task result on the UI control.
ContinueWith:
Consider the following code that displays the result of completed task on the UI label.- public void ContinueWithOperation()
- {
- CancellationTokenSource source = new CancellationTokenSource();
- source.CancelAfter(TimeSpan.FromSeconds(1));
- Task < string > t = Task.Run(() = > LongRunningOperation("Continuewith", 500));
- t.ContinueWith((t1) = >
- {
- if (t1.IsCompleted && !t1.IsFaulted && !t1.IsCanceled) UpdateUI(t1.Result);
- });
- }
- private void UpdateUI(string s)
- {
- label1.Text = s;
- }
When the preceding code is executed the following runtime exception occurs.This exception occurs because the Continuation operation, the UpdateUI operation, runs on a different thread. In that case a thread will be provided by the default threadschedular ThreadPool and it doesn't have any information about the Synchronization context on which to run.
To avoid an exception, one must pass the thread scheduler that passes data on the UI SynchronizationContenxt. In the following code the TaskScheduler.FromCurrentSynchronizationContext() method passes the UI-related thread scheduler.- t.ContinueWith((t1) = >
- {
- if (t1.IsCompleted && !t1.IsFaulted && !t1.IsCanceled) UpdateUI(t1.Result);
- }, TaskScheduler.FromCurrentSynchronizationContext());
- await: Consider below code which display result of completion on UI.
- public async void AsyncOperation()
- {
- try
- {
- string t = await Task.Run(() = > LongRunningOperation("AsyncOperation",
- 10000));
- UpdateUI(t);
- }
- catch (Exception ex)
- {
- MessageBox.Show(ex.Message);
- }
- }
So if one must post data on the UI then await is a good option because there is no need for extra care/code to post data on the UI. - Handling Exception and Cancellation
Consider the following example code for the ContinueWith and await methods for handing exceptions and a cancelled task.
ContinueWith
The following is sample code of how the exception/cancellation is handled using ContinueWith.- public void ContinueWithOperationCancellation()
- {
- CancellationTokenSource source = new CancellationTokenSource();
- source.Cancel();
- Task < string > t = Task.Run(() = > LongRunningOperationCancellation("Continuewith", 1500,
- source.Token), source.Token);
- t.ContinueWith((t1) = >
- {
- if (t1.Status == TaskStatus.RanToCompletion) Console.WriteLine(t1.Result);
- else if (t1.IsCanceled) Console.WriteLine("Task cancelled");
- else if (t.IsFaulted)
- {
- Console.WriteLine("Error: " + t.Exception.Message);
- }
- });
- }
- t.ContinueWith(
- (antecedent) => { },
- TaskContinuationOptions.OnlyOnRanToCompletion);
The following is sample code of how the exception/cancellation is handled using await.- public async void AsyncOperationCancellation()
- {
- try
- {
- CancellationTokenSource source = new CancellationTokenSource();
- source.Cancel();
- string t = await Task.Run(() = > LongRunningOperationCancellation("AsyncOperation", 2000, source.Token),
- source.Token);
- Console.WriteLine(t);
- }
- catch (TaskCanceledException ex)
- {
- Console.WriteLine(ex.Message);
- }
- catch (Exception ex)
- {
- Console.WriteLine(ex.Message);
- }
- }
So from the preceding example my view is cancellation/exception handling is done in a very clean way when one uses continuation.
The following is code for the LongRunningOperationCancellation method.- private string LongRunningOperationCancellation(string s, int sec,
- CancellationToken ct)
- {
- ct.ThrowIfCancellationRequested();
- Thread.Sleep(sec);
- return s + " Completed";
- }
- Complex flow
Consider the following function for the calculation a factorial of a number.- public KeyValuePair<int, string> Factorial(int i)
- {
- KeyValuePair<int, string> kv;
- int fact = 1;
- for (int j = 1; j <= i; j++)
- fact *= j;
- string s = "factorial no " + i.ToString() + ":" + fact.ToString();
- kv = new KeyValuePair<int, string>(i, s);
- return kv;
- }
Now the problem statement is the preceding function to calculate the factorial of a number from 1 to 5.
ContinueWith
The following is code to calculate the factorial of the numbers between 1 and 5. The expectation from the code below is to calculate the factorial of a number then display it in order and once that is done a “Done” message is printed on the console.- public void ContinueWithFactorial()
- {
- for (int i = 1; i < 6; i++)
- {
- int temp = i;
- Task < KeyValuePair < int, string >> t = Task.Run(() = > Factorial(temp));
- t.ContinueWith((t1) = >
- {
- KeyValuePair < int, string > kv = t1.Result;
- Console.WriteLine(kv.Value);
- });
- }
- Console.WriteLine("Done");
- }
So to resolve the problem with the preceding code, the code must be refactored like this:- public void FactContinueWithSeq(int i)
- {
- Task < KeyValuePair < int, string >> t = Task.Run(() = > Factorial(i));
- var ct = t.ContinueWith(((t1) = >
- {
- KeyValuePair < int, string > kv = t1.Result;
- int seed = kv.Key;
- if (seed < 6)
- {
- Console.WriteLine(kv.Value);
- seed++;
- FactContinueWithSeq(seed);
- }
- else
- {
- Console.WriteLine("Done");
- return;
- }
- }));
- }
In the preceding code, to maintain sequence, a task must be fired one by one. And for that once one task completes its execution Contuation on it using the ContinueWith method the function is called again. It's like doing recursive calling to the function.
And to display the “Done” message at the end it is necessary to check for the seed to function. That checks the seed value whether it increases by 6 or not.
But now there is the need for attaching a continuation on the completion of FactContinueWithSeq. To satisfy this the following code must be done.- TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
- public Task<string> FactAsyncTask { get { return tcs.Task; } }
- public void FactContinueWithSeqAsync(int i)
- {
- Task<KeyValuePair<int, string>> t = Task.Run(() => Factorial(i));
- var ct = t.ContinueWith(((t1) =>
- {
- KeyValuePair<int, string> kv = t1.Result;
- int seed = kv.Key;
- if (seed < 5)
- {
- Console.WriteLine(kv.Value);
- seed++;
- FactContinueWithSeqAsync(seed);
- }
- else
- {
- tcs.SetResult("Execution done");
- }
- }));
- }
- p.FactContinueWithSeqAsync(1);
- Task<string> t = p.FactAsyncTask;
- t.ContinueWith((t1)=> Console.WriteLine(t.Result));
So one must do a lot of code to provide the expected results, that is to calculate the factorial in sequence, waiting on the calculation and once the calculation is completed a “Done” message is printed on the console.
await
Now the same code using await can be done like this:- public async void AwaitWithFactorial()
- {
- for (int i = 1; i < 6; i++)
- {
- int temp = i;
- Task<KeyValuePair<int, string>> t = Task.Run(() => Factorial(temp));
- await t;
- Console.WriteLine(t.Result.Value);
- }
- Console.WriteLine("Done");
- }
No comments:
Post a Comment