Besides the most common form of calling TaskFactory.StartNew with only the "action" parameter
(1) https://msdn.microsoft.com/en-us/library/dd321439(v=vs.110).aspx
we alse have one method that accepts an extra parameter as the "Cancelation Token"
(2) https://msdn.microsoft.com/en-us/library/dd988458.aspx
My question is, why should we use the call (2) instead of call (1)?
I mean, the example in MSDN for page (2) would also work if I don't pass the Cancellation Token as parameter (because the variable token is accessible from the delegate function. Something like:
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var files = new List<Tuple<string, string, long, DateTime>>();
var t = Task.Factory.StartNew( () => { string dir = "C:\\Windows\\System32\\";
object obj = new Object();
if (Directory.Exists(dir)) {
Parallel.ForEach(Directory.GetFiles(dir),
f => {
if (token.IsCancellationRequested)
token.ThrowIfCancellationRequested();
var fi = new FileInfo(f);
lock(obj) {
files.Add(Tuple.Create(fi.Name, fi.DirectoryName, fi.Length, fi.LastWriteTimeUtc));
}
});
}
}
); //note that I removed the ", token" from here
tokenSource.Cancel();
So is there anything happening underneath when I pass cancellation token to Task.Factory.StartNew?
Thanks
Two things will happen.
If the token has been canceled before StartNew is called it will never start the thread and the task will be in the Canceled state.
If a OperationCanceledException is raised from inside the task and that exception was passed in the same token as StartNew it will cause the returned Task to enter the Cancelled state. If the token associated with the exception is a different token or you did not pass a token in the task will enter the Faulted state.
P.S. You should never call Task.Factory.StartNew without passing in a TaskScheduler because if you don't it can easily cause you to run code on the UI thread that you expected to run on a background thread. Use Task.Run( instead unless you absoultely need to use StartNew, Task.Run has the same CancellationToken behavior as StartNew.
Related
var cts = new CancellationTokenSource();
var token = cts.Token;
Task.Run(() => {
while(true){
//Doing something in the loop
token.WaitHandle.WaitOne(); // Method 1
token.ThrowIfCancellationRequested(); // Method 2
}
}, token);
Out of these two methods of cancelling a Task, Method 2 is what I understand. But, Method 1 is posing a problem in my understanding, because what I read is it blocks the execution. If it is a blocking call, that means our code is not executing. Then how can this be a method of cancellation? To cancel means, a running task is cancelled, where as in this case when we are waiting, the task is already not executing our code.
Is there any other way how to observe cancellation token besides checking it in loop ?
while (moreToDo)
{
// Poll on this property if you have to do
// other cleanup before throwing.
if (cancellationToken.IsCancellationRequested)
{
// Clean up here, then...
cancellationToken.ThrowIfCancellationRequested();
}
}
Example from: https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/task-cancellation
I want cancel task when method is running more then 5seconds. But processing VeryLong method can takes more then 5 seconds.
var cancelationTokenSource = new CancellationTokenSource(5000);
Task.Factory.StartNew(
() =>
{
VeryLongMethod();
}, cancelationTokenSource.Token
);
As I was suggested I tryed register callback, but after timeout, the very long method was still executed.
var cancellationTokenSource = new CancellationTokenSource(20000);
await Task.Factory.StartNew
(
() =>
{
using (cancellationTokenSource.Token.Register(() =>
{
Program.Console.WriteToConsole("Failed on timeout.");
try
{
cancellationTokenSource.Token.ThrowIfCancellationRequested();
}
catch (Exception e)
{
Console.WriteLine("Action was processed already");
}
}))
{
VeryLongMethod();
}
}, cancellationTokenSource.Token
);
You can register a callback which will be called when the token is cancelled:
using(var reg = cancellationToken.Register(() => { /* cancellation logic */ }))
{
// your code
}
Update: To your updated question, you can tell a cancellation token source that it must cancel its token after a period of time, like this:
cancelationTokenSource.CancelAfter(TimeSpan.FromSeconds(5));
In short, no. Pooling periodically is the only way we can ever check if a task is intended to be cancelled. CancellationToken doesn't provides any other means of notification to check for it, IsCancellationRequested and ThrowIfCancellationRequested being the only ways we can ever know we must cancel.
That implies that with the current async-await task-based design, cancellable methods must periodically check on themselves if the caller wants to cancel, and provide an exit point themselves. There is no direct way of cancelling a method without changing it to "cooperate" with the cancellation system right now.
It doesn't needs a loop in itself, but some form of pooling is needed for the check anyway. A loop is a typical construct, but anything else can be used.
I have this line, which does a blocking (synchronous) web service call, and works fine:
var response = client.Execute<DataRequestResponse>(request);
(The var represents IRestResponse<DataRequestResponse>)
But, now I want to be able to cancel it (from another thread). (I found this similar question, but my code must stay sync - the code changes have to stay localized in this function.)
I found CancellationTokenSource, and an ExecuteTaskAsync() which takes a CancellationToken. (See https://stackoverflow.com/a/21779724/841830) That sounds like it could do the job. I got as far as this code:
var cancellationTokenSource = new CancellationTokenSource();
var task = client.ExecuteTaskAsync(request, cancellationTokenSource.Token);
task.Wait();
var response = task.Result;
The last line refuses to compile, telling me it cannot do an implicit cast. So I tried an explicit cast:
IRestResponse<DataRequestResponse> response = task.Result as IRestResponse<DataRequestResponse>;
That compiles, runs, but then crashes (throws a NullReferenceException, saying "Object reference not set to an instance of an object").
(By the way, once I have this working, then the cancellationTokenSource.Token will of course be passed in from the master thread, and I will also be adding some code to detect when an abort happened: I will throw an exception.)
My back-up plan is just to abort the whole thread this is running in. Crude, but that is actually good enough at the moment. But I'd rather be doing it "properly" if I can.
MORE INFORMATION:
The sync Execute call is here: https://github.com/restsharp/RestSharp/blob/master/RestSharp/RestClient.Sync.cs#L55 where it ends up calling Http.AsGet() or Http.AsPost(), which then ends up here: https://github.com/restsharp/RestSharp/blob/master/RestSharp/Http.Sync.cs#L194
In other words, under the covers RestSharp is using HttpWebRequest.GetResponse. That has an Abort function, but being a sync function (i.e. which does not return until the request is done) that is not much use to me!
The async counterpart of your call
var response = client.Execute<DataRequestResponse>(request);
is the public virtual Task<IRestResponse<T>> ExecuteTaskAsync<T>(IRestRequest request, CancellationToken token) RestSharp async method.
It takes a cancellation token, and returns a task with the correct return type signature. To use it and wait for its (cancellable) completion, you could change your sample code to:
var cancellationTokenSource = new CancellationTokenSource();
// ...
var task = client.ExecuteTaskAsync<DataRequestResponse>(
request,
cancellationTokenSource.Token);
// this will wait for completion and throw on cancellation.
var response = task.Result;
Given this sync call:
var response = client.Execute<MyData>(request);
process(response);
Change it to:
var response = null;
EventWaitHandle handle = new AutoResetEvent (false);
client.ExecuteAsync<MyData>(request, r => {
response = r;
handle.Set();
});
handle.WaitOne();
process(response);
That is equivalent to the sync version, no benefit is gained. Here is one way to then add in the abort functionality:
bool volatile cancelAllRequests = false;
...
var response = null;
EventWaitHandle handle = new AutoResetEvent (false);
RestRequestAsyncHandle asyncRequest = client.ExecuteAsync<MyData>(request, r => {
response = r;
handle.Set();
});
while(true){
if(handle.WaitOne(250))break; //Returns true if async operation finished
if(cancelAllRequests){
asyncRequest.WebRequest.Abort();
return;
}
}
process(response);
(Sorry, couldn't wait for the bounty to bear fruit, so had to work this out for myself, by trial and error this afternoon...)
I read about the Cancellation methods but based on my understanding they all provide control over tasks from outside the task, but if i want to cancel a task from the inside.
For example in this pseudo code:
Task tasks = new Task(() =>
{
bool exists = CheckFromDB();
if (!exists)
break;
}
Can i cancel a task from the inside?
The only idea i got is to trigger an exception inside the task and handle it from outside but surly that is not the way to do.
If you want to truly Cancel the task (e.g. after it's completed the Status property would be set to Canceled), you can do it this way:
var cts = new CancellationTokenSource();
var token = cts.Token;
Task innerCancel = new Task(
() =>
{
if (!CheckFromDB())
{
cts.Cancel();
}
token.ThrowIfCancellationRequested();
},
token);
When you use return task won't be actually canceled, but rather code after the return statement won't be executed, and the task itself will have its state as RanToCompletion.
On a side note it's advised to use Task.Factory.StartNew for .NET 4 and Task.Run for .NET 4.5 instead of constructor to create tasks.
The lambda expression code inside a task behaves just the same as in any other method. If you want to end the task for some reason you can just simply return:
Task task = Task.Run(() =>
{
if(!CheckFromDB())
{
return;
}
}
You can also throw an exception. That will end the task and mark it as faulted but there's no reason to do so if you can avoid it. If you are really responding to some problem that you can't overcome, then yes, just throw an exception.
I have the following code to build an advanced data structure which is pulled from SQL Server, then when the retrevial of that data is complete I update the UI. The code used is
private void BuildSelectedTreeViewSectionAsync(TreeNode selectedNode)
{
// Initialise.
SqlServer instance = null;
SqlServer.Database database = null;
// Build and expand the TreeNode.
Task task = null;
task = Task.Factory.StartNew(() => {
string[] tmpStrArr = selectedNode.Text.Split(' ');
string strDatabaseName = tmpStrArr[0];
instance = SqlServer.Instance(this.conn);
database = instance.GetDatabaseFromName(strDatabaseName);
}).ContinueWith(cont => {
instance.BuildTreeViewForSelectedDatabase(this.customTreeViewSql,
selectedNode, database);
selectedNode.Expand();
task.Dispose();
}, CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion,
this.MainUiScheduler);
}
This works as it should on my main development machine; that is, it completes the build of the database object, then in the continuation update the UI and disposes the task (Task object).
However, I have been doing some testing on another machine and I get an InvalidOperationException, this is due to the task.Dispose() on task which still in the Running state, but the continuation cont should never fire unless the task has ran to completion.
Here's what the code looks like in the debugger when the exception is thrown:
I am aware that it almost always unneccessary to call Dispose on tasks. This question is more about why the continuation is firing at all here?**
The reason for this is simple, you are calling Dispose on the continuation itself and not on the first task
Your code consists of:
Task task = null;
var task = <task 1>.ContinueWith(t => {
/* task 2 */
task.Dispose();
});
In the above code, task is equal to the continuation (ContinueWith doesn't pass back the original Task, it passes the continuation) and that's what's getting captured in the closure you're passing to ContinueWith.
You can test this by comparing the references of the Task parameter passed into the ContinueWith method with task:
Task task = null;
var task = <task 1>.ContinueWith(t => {
/* task 2 */
if (object.ReferenceEquals(t, task))
throw new InvalidOperationException("Trying to dispose of myself!");
task.Dispose();
});
In order to dispose of the first, you need to break it up into two Task variables and capture the first Task, like so:
var task1 = <task 1>;
var task2 = task1.ContinueWith(t => {
// Dispose of task1 when done.
using (task1)
{
// Do task 2.
}
});
However, because the previous Task is passed to you as a parameter in the ContinueWith method, you don't need to capture task in the closure at all, you can simply call Dispose on the Task passed as a parameter to you:
var task = <task 1>.ContinueWith(t => {
// t = task 1
// task = task 2
// Dispose of task 1 when done.
using (t)
{
// Do task 2.
}
});
I'm pretty sure you are trying to do above is equivelent to:
task = Task.Factory.StartNew(() => ...);
task.ContinueWith(cont => { ... task.Dispose(); });
However, what gets assigned to task variable with your code will be the ContinueWith work item, not the origninal StartNew work item.
More importantly, you probably don't even need to worry about task.Dispose() in this scenario.
The only time there is any real value in doing task.Dispose() is when there is a task.Wait() involved somewhere, which allocates an OS wait handle resource under the covers.
More info:
http://social.msdn.microsoft.com/Forums/en/parallelextensions/thread/7b3a42e5-4ebf-405a-8ee6-bcd2f0214f85