c# - Trying to Dispose task while it is executing code - c#

While "worker" is executing piece of code, I'm closing the whole window and I want to dispose it on closing that window because it is finishing it's code otherwise.
Task worker = Task.Factory.StartNew(new Action(() =>
{
// some code here
}
Unfortunetly, when I call worker.Dispose() in Close() there is an Exception:
A task may only be disposed if it is in a completion state
(RanToCompletion, Faulted or Canceled)
Any suggestions how I can dispose it while it is working?

You need to write your code so that your task will accept a cancellation token. That's basically just a flag that can be checked by the code in your task, which if updated you would provide logic to handle, having your task's logic safely handle how to terminate its execution, rather than simply stopping at some unknown state. Running the below sample code in LinqPad should give you a reasonable example of what's going on:
void Main()
{
var form = new Form();
var label = new Label(){Text = string.Format("{0:HH:mm:ss}", DateTime.UtcNow), AutoSize = true};
form.Controls.Add(label);
var taskController = new CancellationTokenSource();
var token = taskController.Token;
var task = Task.Run(() =>
{
for (var i=0; i<100; i++)
{
var text = string.Format("{0:HH:mm:ss}", DateTime.UtcNow);
Console.WriteLine(text); //lets us see what the task does after the form's closed
label.Text = text;
if (token.IsCancellationRequested)
{
Console.WriteLine("Cancellation Token Detected");
break;
}
Thread.Sleep(1000);
}
}, token);
form.FormClosed += new FormClosedEventHandler(
(object sender, FormClosedEventArgs e) =>
{taskController.Cancel();}
);
form.Show();
}
Key Points:
Create an instance of CancellationTokenSource. This is a simple object which will allow you to communicate when you wish to cancel to your task.
var taskController = new CancellationTokenSource();
Fetch the token from this source
var token = taskController.Token;
Run the task, passing a reference to the token
var task = Task.Run(() =>
{
//...
, token
}
Add logic within the task to check the status of this token at suitable points, & handle it appropriately.
if (token.IsCancellationRequested)
{
Console.WriteLine("Cancellation Token Detected");
break;
}
Add logic to call the Cancel method when you wish to cancel the task. In the above code I've put this under the Form's FormClosed event handler's logic:
taskController.Cancel();
See https://binary-studio.com/2015/10/23/task-cancellation-in-c-and-things-you-should-know-about-it/ for a good write up / related ways to cancel a task.
Side Note
In the above example I was a bit lazy. Each iteration of the loop I check the cancellation token; but then (if not cancelled) wait 1 second before looping. Since the cancel logic only comes into play when the if statement is evaluated that means that we have to wait 1 second for the cancellation to take effect, which isn't great; if that delay was larger (e.g. 5 minutes), it could be really painful. One solution is outlined here: https://stackoverflow.com/a/17610886/361842
i.e. replace
if (token.IsCancellationRequested)
{
Console.WriteLine("Cancellation Token Detected");
break;
}
Thread.Sleep(1000);
with
if (token.IsCancellationRequested)
{
Console.WriteLine("Cancellation Token Detected");
break;
}
token.WaitHandle.WaitOne(1000);
See https://learn.microsoft.com/en-us/dotnet/api/system.threading.waithandle.waitone?view=netframework-4.7.2 for documentation on the WaitOne method.

Try using a cancellation token.
var cancellationTokenSource = new CancellationTokenSource();
var t = Task.Factory.StartNew(() =>
{
// Your code here
}, cancellationTokenSource.Token).ContinueWith(task =>
{
if (!task.IsCompleted || task.IsFaulted)
{
// log error
}
}, cancellationTokenSource.Token);
Keep the cancellationTokenSource handy and cancel it in your Close()

Related

GetContextAsync() with Cancellation Support

So I'm spinning up a HttpListener to wait for an OAuth2 response. In an ideal world, this is only going to be alive for a few seconds while the user logs in in the browser and we get posted the token.
I'd also like for this to have a CancellationToken so that the user can stop listening after a delay should they so wish.
My initial idea was to use something along the lines of:
_listener.Start();
Task<HttpListenerContext> t = _listener.GetContextAsync();
while (!cancelled.IsCancellationRequested)
{
if (t.IsCompleted)
{
break;
}
await Task.Run(() => Thread.Sleep(100));
}
HttpListenerContext ctx = t.Result;
//...
_listener.Stop();
But that doesn't sit right with me for so many reasons (weird async usage, polling, etc.).
So then I thought I might be able to use the synchronous version _listener.GetContext() in conjunction with Task.Run(func<T>, CancellationToken):
_listener.Start()
HttpListenerContext ctx = await Task.Run(() => _listener.GetContext(), cancelled);
//...
_listener.Stop();
This is a little better, the code's at least tidier, although it seems hacky using a synchronous version of the method asynchronously with a Task...
However this doesn't behave how I'd expect (aborting the running task when the token is cancelled).
This strikes me as something that really ought to be fairly simple to do so I assume I'm missing something.
So my question is thus... How do I listen asynchronously with a HttpListener in a cancellable fashion?
Because the GetContextAsync method does not support cancellation, it basically means that it is unlikely you can cancel the actual IO operation, yet unlikely to cancel the Task returned by the method, until you Abort or Stop the HttpListener. So the main focus here is always a hack that returns the control flow to your code.
While both the answers from #guru-stron and #peter-csala should do the trick, I just wanted to share another way without having to use Task.WhenAny.
You could wrap the task with a TaskCompletionSource like this:
public static class TaskExtensions
{
public static Task<T> AsCancellable<T>(this Task<T> task, CancellationToken token)
{
if (!token.CanBeCanceled)
{
return task;
}
var tcs = new TaskCompletionSource<T>();
// This cancels the returned task:
// 1. If the token has been canceled, it cancels the TCS straightaway
// 2. Otherwise, it attempts to cancel the TCS whenever
// the token indicates cancelled
token.Register(() => tcs.TrySetCanceled(token),
useSynchronizationContext: false);
task.ContinueWith(t =>
{
// Complete the TCS per task status
// If the TCS has been cancelled, this continuation does nothing
if (task.IsCanceled)
{
tcs.TrySetCanceled();
}
else if (task.IsFaulted)
{
tcs.TrySetException(t.Exception);
}
else
{
tcs.TrySetResult(t.Result);
}
},
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
return tcs.Task;
}
}
And flow the control like this:
var cts = new CancellationTokenSource();
cts.CancelAfter(3000);
try
{
var context = await listener.GetContextAsync().AsCancellable(cts.Token);
}
catch (TaskCanceledException)
{
// ...
}
I would suggest creating cancelable infinite task (Task.Delay(Timeout.Infinite, token) for example) and use Task.WhenAny. Something like that:
var cts = new CancellationTokenSource(); // token source controled by consumer "outside"
var token = cts.Token;
var httpListener = new HttpListener();
httpListener.Start();
var t = httpListener.GetContextAsync();
// to cancel the infinite delay task if listener finishes first
var localCts = CancellationTokenSource.CreateLinkedTokenSource(token);
var completed = await Task.WhenAny(t, Task.Delay(Timeout.Infinite, localCts.Token));
if (completed == t) // check that completed task is one from listener
{
localCts.Cancel(); // cancel the infinite task
HttpListenerContext ctx = t.Result;
//...
}
httpListener.Stop();
Here is yet another solution:
var cancellationSignal = new TaskCompletionSource<object>();
var contextTask = _listener.GetContextAsync();
using (cancelled.Register(state => ((TaskCompletionSource<object>)state).TrySetResult(null), cancellationSignal))
{
if (contextTask != await Task.WhenAny(contextTask, cancellationSignal.Task).ConfigureAwait(false))
break; //task is cancelled
}
Because we can't await the CancellationToken that's why have to apply the following trick
The CancellationToken does expose a Register method, where we can define a callback which will be called whenever the cancellation occurs
Here we can provide a delegate which sets an awaitable to completed
So, we can await that task
In order to create a Task which is set to completed whenever the cancellation occurs I've used TaskCompletionSource. You could also use SemaphoreSlim or any other signalling object which has async wait, like AsyncManualResetEvent.
So, we pass the cancellationSignal to the Register as a state parameter
Inside the delegate we have to cast it back to TCS to be able to call the TrySetResult on it
Inside the using block we await a Task.WhenAny
It will return that Task which finishes first
If that Task is the cancellation then we can break / return / throw ...
If that Task is the contextTask then we can continue the normal flow

Is there a proper way to cancel an asynchronous task?

I encountered a problem how to properly cancel an asynchronous task.
Here is some draft.
My entry point runs two asynchronous task. The first task does some 'long' work and the second one cancels it.
Entry point:
private static void Main()
{
var ctc = new CancellationTokenSource();
var cancellable = ExecuteLongCancellableMethod(ctc.Token);
var cancelationTask = Task.Run(() =>
{
Thread.Sleep(2000);
Console.WriteLine("[Before cancellation]");
ctc.Cancel();
});
try
{
Task.WaitAll(cancellable, cancelationTask);
}
catch (Exception e)
{
Console.WriteLine($"An exception occurred with type {e.GetType().Name}");
}
}
Method that returns cancel-able task:
private static Task ExecuteLongCancellableMethod(CancellationToken token)
{
return Task.Run(() =>
{
token.ThrowIfCancellationRequested();
Console.WriteLine("1st");
Thread.Sleep(1000);
Console.WriteLine("2nd");
Thread.Sleep(1000);
Console.WriteLine("3rd");
Thread.Sleep(1000);
Console.WriteLine("4th");
Thread.Sleep(1000);
Console.WriteLine("[Completed]");
}, token);
}
My purpose is to stop writing '1st','2nd','3rd' immediately after cancellation is called. But I get following results:
1st
2nd
3rd
[Before cancellation]
4th
[Completed]
For obvious reason I didn't get an exception that throws when cancellation is requested. So I tried to rewrite method as following:
private static Task ExecuteLongCancellableAdvancedMethod(CancellationToken token)
{
return Task.Run(() =>
{
var actions = new List<Action>
{
() => Console.WriteLine("1st"),
() => Console.WriteLine("2nd"),
() => Console.WriteLine("3rd"),
() => Console.WriteLine("4th"),
() => Console.WriteLine("[Completed]")
};
foreach (var action in actions)
{
token.ThrowIfCancellationRequested();
action.Invoke();
Thread.Sleep(1000);
}
}, token);
}
And now I got what I want:
1st
2nd
[Before cancellation]
3rd
An exception occurred with type AggregateException
But I guess creating of a collection of Action delegates and looping through it is not the most convenient way to deal with my problem.
So what's the proper way to do it? And why do I need to pass my cancellation token into Task.Run method as the second argument?
The Task won't cancel its self, it is up you you to detect the cancellation request and cleanly abort your work. That's what token.ThrowIfCancellationRequested(); does.
You should place those checks throughout your code, in places where execution can be cleanly stopped, or rolled back to a safe state.
In your second example, you call it once per iteration of the loop, and it works fine. The first example only calls it once, at the very start. If the token hasn't been canceled by that point, the task will run to completion, just like you are seeing.
If you changed it to look like this, you would also see the results you expect.
return Task.Run(() =>
{
token.ThrowIfCancellationRequested();
Console.WriteLine("1st");
Thread.Sleep(1000);
token.ThrowIfCancellationRequested();
Console.WriteLine("2nd");
Thread.Sleep(1000);
token.ThrowIfCancellationRequested();
Console.WriteLine("3rd");
Thread.Sleep(1000);
token.ThrowIfCancellationRequested();
Console.WriteLine("4th");
Thread.Sleep(1000);
Console.WriteLine("[Completed]");
}, token);

C#: CancellationToken doesn't cancel blocking method

.NET 4.5.1: It appears, I can't cancel a blocking method running inside a task using the CancellationTokenSource built-in timeout.
class Program
{
static void Main(string[] args)
{
var cts = new System.Threading.CancellationTokenSource();
System.Console.CancelKeyPress += (s, e) =>
{
e.Cancel = true;
cts.Cancel();
};
MainAsync(args, cts.Token).Wait();
}
// MainAsync from http://stackoverflow.com/questions/9208921/async-on-main-method-of-console-app
static async Task MainAsync(string[] args, System.Threading.CancellationToken token)
{
Console.WriteLine("Starting MainAsync");
var cts = new System.Threading.CancellationTokenSource(3000);
var task = Task.Factory.StartNew(() =>
{
Console.WriteLine("Starting task...");
var t = new System.Net.Sockets.TcpClient();
var buffer = new byte[t.ReceiveBufferSize];
t.Connect(new System.Net.IPEndPoint(System.Net.IPAddress.Parse("127.0.0.1"), 1337));
Console.WriteLine("Recieving...");
t.Client.Receive(buffer);
Console.WriteLine("Finished Recieving...");
return true;
}, cts.Token);
var success = await task;
Console.WriteLine("Did the task complete succesfuly?", success);
}
}
The output from the above Short, Self Contained, Correct Example (I hope it's correct) is:
Starting MainAsync
Starting task...
Recieving...
Why does the task doesn't cancel, no exception is thrown?
As I state on my blog, "You keep using that CancellationToken there. I do not think it means what you think it means."
In particular, the CancellationToken passed to StartNew will only cancel the starting of the delegate. If you want the delegate itself to support cancellation, then the delegate itself will have to observe the CancellationToken.
I am not sure but I guess you are confusing "requesting a cancellation" with "terminating or aborting a thread/task".
These are two completly different things. According to the description about the Canellation in Managerd Threads the provided functionality enables you to send something like a signal, indicating, that the operation in progress shall be stopped.
How and if you react on that signal is - as a programmer - up to you.
In your example you've started a new task
var task = Task.Factory.StartNew(() =>
{
Console.WriteLine("Starting task...");
var t = new System.Net.Sockets.TcpClient();
var buffer = new byte[t.ReceiveBufferSize];
t.Connect(new System.Net.IPEndPoint(System.Net.IPAddress.Parse("127.0.0.1"), 1337));
Console.WriteLine("Recieving...");
t.Client.Receive(buffer);
Console.WriteLine("Finished Recieving...");
return true;
}, cts.Token);
which does not handle the cancellation nor is it suitable to do so.
Canellation would be used e.g. if you want to break out of a loop with many iterations - therefore you would check in each iteration whether the CancellationToken.IsCancellationRequested has been set to true or not.
If so, you can react accordingly.
What you want is to abort the thread that's behind the current task which is in my opinion only possible by creating a new instance of the Thread class for yourself and handle the abort accordingly.

Can cancellation token be used at tasks method within?

I've just started working with tasks and I've come to some things I don't quite understand about calling methods within the task. I have started a new task like this:
var ts = new CancellationTokenSource();
var token = ts.Token;
Task.Run(() => Control(), token);
void Control()
{
while(!token.IsCancellationRequested)
{
token.ThrowIfCancellationRequested();
switch(ENUM)
{
case SOMETHING:
StartSomething();
break;
}
Task.Delay(50, token).wait();
}
}
Now I don't understand the behavior of StartSomething() once token has been cancelled. What if StartSomething() as well contains a while loop, can I as well use?
!token.IsCancellationRequested
and
token.ThrowIfCancellationRequested();
As well, if the Cancellation exception is being thrown inside that StartSomething() loop, will it instantly cancel task?
Yes, you can easily pass the same token onto StartSomething and exceptions from it will bubble up to Control and cancel the task. If you don't then it will keep running even if the CancellationTokenwas cancelled until it returns control toControl` that observes the token:
void StartSomething(CancellationToken token)
{
while (true)
{
token.ThrowIfCancellationRequested(); // Will cancel the task.
// ...
}
}
Keep in mind though that token.ThrowIfCancellationRequested() will raise exception and the task will be canceled while !token.IsCancellationRequested will simply complete the task without marking it as canceled.

Cancelling a task which retrieves URLs asynchronously

I'm having a bit of a problem finding out how to cancel this task in C#. I don't exactly have a strong understanding of handling threads and I've tried Googling for some simple code examples to help me out but I've gotten really no where. Here's the piece of code I'm working on:
var tasks = urls.Select(url => Task.Factory.StartNew(
state =>
{
using (var client = new WebClient())
{
lock (this)
{
// code to download stuff from URL
}
}
}, url)).ToArray();
try
{
Task.WaitAll(tasks);
}
catch (Exception e)
{
textBox2.AppendText("Error: " + e.ToString());
}
Where "urls" is an array of URLs. Is there a simple way to make it so that, when I click a button in my program, the downloading of the URLs is stopped completely? Also, the code snippet I pasted is in a function which backgroundWorker1 calls, which I suppose might make things a bit more complicated. (The reason why I have a backgroundWorker is so the UI doesn't lock up while it's downloading URLs.)
If that in any way is a bit confusing, here is an outline of what I was trying to achieve with my code:
I have an array of URLs, I'd like to download every URL asynchronously without locking up the UI.
I'd preferably like the user to stop the program from downloading URLs by clicking a button, pretty much cancelling the thread.
When the user clicks the button again, the program downloads the URLs all over again from that array.
Thanks in advance.
Don't know if this is right way to do this or not, but I have been able to cancel tasks using the following code. I have created a form with ListBox and ProgressBar so I'm raising and handling ProgressChanged event of BackgroundWorker. Hope this helps you in some way.
void bw_DoWork(object sender, DoWorkEventArgs e)
{
CancellationTokenSource _tokenSource = new CancellationTokenSource();
CancellationToken _token = _tokenSource.Token;
var urls = e.Argument as IEnumerable<string>;
_token = new CancellationToken();
if (urls == null) return;
var i = 0;
var a = 100 / urls.Count();
var sb = new StringBuilder();
var t = urls.Select(url => Task.Factory.StartNew(
(u) =>{
using (var wc = new WebClient())
{
lock (this){
var s = wc.DownloadString(u.ToString());
sb.AppendFormat("{1}:{0}\r\n", "", u);
}
}
if (Worker.CancellationPending){
//MAGIC HAPPENS HERE, IF BackgroundWorker IS REQUESTED
//TO CANCEL, WE CANCEL CancellationTokenSource
_tokenSource.Cancel();
}
//IF CANCELATION REQUESTED VIA CancellationTokenSource
//THROW EXCEPTION WHICH WILL ADD TO AggreegateException
_token.ThrowIfCancellationRequested();
//YOU CAN IGNORE FOLLOWING 2 LINES
i += a;
Worker.ReportProgress(i, u);
}, url, _token)).ToArray();
try
{
Task.WaitAll(t);
}
catch (AggregateException age)
{
if (age.InnerException is OperationCanceledException)
sb.Append("Task canceled");
}
catch (Exception ex)
{
sb.Append(ex.Message);
}
e.Result = sb;
}
With WebClient, you can use the CancelAsync method to cancel an asynchronous operation.
To cancel the tasks you're starting via Factory.StartNew, you should use a CancellationTokenSource. You need to pass CancellationTokenSource.Token to the tasks (and you can ask if the token is canceled already using token.IsCancellationRequested), and you'd call CancellationTokenSource.Cancel() to set the token as cancelled.

Categories

Resources