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.
Related
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()
my challenge is a rather common one, I have a heavily populated treeview that i want to filter. To do this i want to have a text box that the user enters in their filter text and after the treeview is filtered to show nodes with that particular filter text in their header.
so what i've opted to do is to have a textbox that then has a text change event that has a delay before it starts it filtering process, now obviously if the filter text changes before the delay finishes i want to cancel the process and start a new one with the new fitler text.
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Controls;
namespace pav.skillsToCompetenciesMapper.Views
{
public partial class MapSkillsPage : Page
{
CancellationTokenSource cts;
private async void Search_TEXTBOX_TextChanged(object sender, TextChangedEventArgs e)
{
if (cts != null) cts.Cancel();
var searchText = Search_TEXTBOX.Text;
try
{
using (cts = cts ?? new CancellationTokenSource())
await Task.Delay(3000, cts.Token).ContinueWith(tr =>
{
var st = searchText;
//Do search here
}, TaskContinuationOptions.NotOnCanceled);
}
catch (OperationCanceledException) { }
finally { cts = null; }
}
}
}
Now the above seems to work for me, I'm just worried that this try catch solution is a bit clunky, it really seems as if i should be able to use the TaskContinuation.OnlyOnCanceled to avoid using a try catch for logic. just seems like a code smell to me, but that's a side note.
My real problem occurs when i try to actually search the Treeview like so where the above "Do Search Here" comment is
foreach (TreeViewItem category in Abilities_TreeView.Items)
foreach (DragableTreeViewItem ability in category.Items)
if (!ability.Header.ToString().Contains(filterText))
ability.Visibility = Visibility.Hidden;
Any help on this would be greatly appreciated, my suspicion is that it has something to do with trying to access the UI thread from a background thread, but i'm not 100% sure if i'm barking up the right tree.
If you don't want to handle the OperationCanceledException, you could use the overload of the ContinueWith method that only accepts a continuation action and check the value of the IsCanceled property inside this action:
try
{
using (cts = cts ?? new CancellationTokenSource())
await Task.Delay(3000, cts.Token).ContinueWith(tr =>
{
if (!tr.IsCanceled)
{
var st = searchText;
//Do search here
}
});
}
finally { cts = null; }
thanks, sorry i was a bit trigger happy, and didn't finish asking my question
You can't access the TreeView from any other thread than the dispatcher thread that it was originally created on, but you can make sure that the continuation action will be executed on this thread by using an overload that accepts a TaskScheduler:
await Task.Delay(3000, cts.Token).ContinueWith(tr =>
{
if (!tr.IsCanceled)
{
var st = searchText;
//Do search here
}
}, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
To load several large files quickly, I launched backgroundworkers as the number of files.
Each backgroundworker needs long time to load its file respectively. While they load their files, I'd like to stop all loading. I understand backgroundworker.CancelAsync() sends cancellation message to the thread, but the thread has no time to accept the message. Because each thread is loading just one file and there is no loop operation to check cancellation. In this case, how can I stop these backgroundworkers?
Let me show my codes here.
//main thread calls 50 child threads.
private List<BackgroundWorker> bgws = new List<BackgroundWorker>();
private bool ChildThreadCompleted;
private void MainThread_DoWork(object sender, DoWorkEventArgs e)
{
// 50 sub threads will be started here
for (int i=1; i<=50; i++)
{
if (mainThread.CancellationPending) return;
BackgroundWorker childThread = new BackgroundWorker();
childThread.WorkerSupportsCancellation = true;
childThread.DoWork += ChildThread_DoWork;
childThread.RunWorkerCompleted += ChildThread_RunWorkerCompleted;
bgws.Add(childThread);
childThread.RunWorkerAsync(i);
}
while (!ChildThreadCompleted)
{
if (mainThread.CancellationPending)
{
foreach (BackgroundWorker thread in bgws)
if (thread.IsBusy) thread.CancelAsync();
}
Application.DoEvents();
}
}
private void ChildThread_DoWork(object sender, DoWorkEventArgs e)
{
int arg = Convert.ToInt32(e.Argument);
System.Threading.Thread.Sleep(1000);
BackgroundWorker thread = (BackgroundWorker)sender;
if (thread.CancellationPending) return;
// In case of loading the image no longer makes sense, I'd like to stop.
// At this point, i can't stop this process.
//loading large file here. Just one image file for example. <= this takes one or more seconds
e.Result = arg;
}
private void ChildThread_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
BackgroundWorker thread = sender as BackgroundWorker;
bgws.Remove(thread);
if (bgws.Count == 0) ChildThreadCompleted = true;
}
Short version
BGW can't do that, not without complex coding. ActionBlock was purpose-built to handle a stream of inputs with cancellation support.
I use the dataflow classes to find, download and proccess thousands of air ticket records every 15 minutes.
Long Version
BackgroundWorker is obsolete, fully replaced by TPL classes like tasks, CancellationToken, IProgress etc.
This particular problem though is best addressed by a higher-level class, ActionBlock.
ActionBlock and the other classes in the TPL Dataflow namespace allow you to create a pipeline of blocks similar to a Powershell pipeline.
Each block runs on its own task, receives an input and passes an output to the next block. You can even specify that one block can process multiple inputs by using multiple tasks.
ActionBlock doesn't produce any output, it only processes inputs. Like most blocks, it has an input buffer and supports cancellation.
A file processing block could look like this. :
var block=new ActionBlock<string>(file => SomeSlowFileProcessing(file));
var files = Directory.EnumerateFiles(someFolder,somePattern);
//Post all files
foreach(file in files)
{
//Post doesn't block
block.Post(file);
}
When we are done using the block, we should tell it we're finished :
block.Complete();
And await asynchronously until all leftover files are processed :
await block.Completion;
You can instruct the block to process multiple messages in parallel by specifying the maximum number of concurrent tasks :
var options = new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 20
};
var block=new ActionBlock<string>(file => SomeSlowFileProcessing(file), options);
Starting 100 threads to process 100 files will probably result in the threads blocking each other. By limiting the number of concurrent tasks you ensure all the CPUs can perform useful work.
You can also stop the block. One way is to "nuke" it by calling the Fault() method :
block.Fault();
try
{
await block.Completion;
}
catch(Exception exc)
{
...
}
This will discard everything left into the input buffer and propagate an exception to the next block in the pipeline. It's as if the block's method threw an exception. In this case there's no other block and await block.Completion; will throw.
Another, more cooperative way is to use a CancellationTokenSource to cancel the block and signal the worker method that it should cancel.
A CancellationTokenSource is a class that can be used to signal cancellation to any task, thread or other code. It does that by providing a CancellationToken whose IsCancellationRequested property becomes true when someone calls Cancel() on the CTS, or its timeout interval expires.
This way, you can provide a timeout feature to your block by creating a CTS with a timeout period :
var cts=new CancellationTokenSource(60000); //Timeout in 1 minute
var options = new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 20,
CancellationToken=cts.Token
};
var block=new ActionBlock<string>(file => SomeSlowFileProcessing(file), options);
//.....
try
{
await block.Completion;
}
catch (OperationCanceledException)
{
Console.WriteLine("Timed out!");
}
A button event can be used to signal cancellation if the CTS is stored in a field :
CancellationTokenSource _cts;
ActionBlock _block;
public void Start_Click(object sender, EventArgs args)
{
//Make sure both the CTS and block are created before setting the fields
var cts=new CancellationTokenSource(60000); //Timeout in 1 minute
var token=cts.Token;
var options = new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 20,
CancellationToken=token
};
var block=new ActionBlock<string>(file => SomeSlowFileProcessing(file,token),
options);
//Once preparation is over ...
_cts=cts;
_block=block;
//Start posting files
...
}
public async void Cancel_Click(object sender, EventArgs args)
{
lblStatus.Text = "Cancelling";
_cts.Cancel();
try
{
await _block.Completion;
}
lblStatus.Text = "Cancelled!";
}
Loading large files with cancellation
Asynchronous file operations also accept a cancellation token, eg FileStream.ReadAsync has an overload that accepts a CancellationToken
This means that the worker method can be cancelled if it's converted to an async method, eg
async Task MySlowMethod(string fileName,CancellationToken token)
{
try
{
using (FileStream SourceStream = File.Open(filename, FileMode.Open))
{
var data = new byte[SourceStream.Length];
await SourceStream.ReadAsync(data, 0, (int)SourceStream.Length,token);
// Use the data
}
}
I am trying to use the following technique to be able to have a worker task executing some operations, with a 10 sec timeout and without blocking the application.
internal void ReadAll()
{
var data = new byte[1];
Task.Factory.StartNew(() =>
{
var ct = new CancellationTokenSource();
var ReadAllTask = Task.Factory.StartNew(() =>
{
// Read all information
// [omit communication exchange via COM port]
ct.Cancel();
}, ct.Token);
// First thread waiting 10s for the worker to finish
ReadAllTask.Wait(10000, ct.Token);
if (ReadAllTask.Status == TaskStatus.RanToCompletion)
{
ReadAllComplete?.Invoke(true);
}
else
{
ct.Cancel();
ReadAllComplete?.Invoke(false);
}
});
}
This method is called by pressing a button. It seems to me that in debug configuration works properly, but not in release configuration where the "first thread" never reach the wait and no event is thrown.
Your code could be a lot simpler than current version. Easiest way to make a non-blocking method for event is to mark it with async keyword and use the await keyword to start the asynchronous read operation from SerialPort.BaseStream property.
Also, CancellationTokenSource could be created with time, after that it get cancelled automatically, and the right way to cancel is to call CancellationToken.ThrowIfCancellationRequested method. async/await mechanism will invoke the event in UI context, so code could be something like this:
// async void is a recommended way to use asynchronous event handlers
private async void btnReadAll_Click(object sebder, EventArgs e)
{
var data = new byte[2];
// cancel source after 10 seconds
var cts = new CancellationTokenSource(10000);
// Read all information
// [omit communication exchange via COM port]
// async operation with BaseStream
var result = await SerialPort.BaseStream.ReadAsync(data, 0, 2, cts.Token);
/*
* if you can't use the BaseStream methods, simply call this method here
* cts.Token.ThrowIfCancellationRequested();
*/
// this code would run only if everything is ok
// check result here in your own way
var boolFlag = result != null;
ReadAllComplete?.Invoke(boolFlag);
}
Here's just a quick rewrite to remove the event and wrap what appears to be a synchronous IO API in an async one. If at all possible you should switch to a true async API and drop the Task.Run.
private CancellationTokenSource cts;
public async void MyButtonhandler(object sender, EventArgs e) {
cts = new CancellationTokenSource();
try {
var result = await Task.Run(() => ReadAll(cts));
if (result) {
//success
} else {
//failure
}
} catch (TaskCanceledException ex) {
}
}
internal async Task<bool> ReadAll(CancellationTokenSource cts) {
byte[] data = new byte[1];
var timeout = TimeSpan.FromSeconds(10);
var ReadAllTask = Task.Run(() => {
// Read all information
// [omit communication exchange via COM port]
}, cts.Token);
if (await Task.WhenAny(ReadAllTask, Task.Delay(timeout)) == ReadAllTask) {
return true;
}
cts.Cancel();
return false;
}
Reading comments and answers to my question I learned a couple of useful things that solve my problem:
CancellationTokenSource can have an implicit timeout
use Task.Run instead Task.Factory.StartNew
don't need to cancel the task, the cts will do the work
Now my code is simpler and it works:
private void Read_All_Button_Click(object sender, RoutedEventArgs e)
{
// Start timedout task that will send all necessary commands
CancellationTokenSource cts = new CancellationTokenSource(10000);
Task.Run(() =>
{
oCommandSets.ReadAll(cts);
}, cts.Token);
}
and
internal void ReadAll(CancellationTokenSource cts)
{
// [communication]
if (cts.IsCancellationRequested)
{
ReadAllComplete?.Invoke(false);
}
else
{
ReadAllComplete?.Invoke(true);
}
}
In any case I need to learn more about multithreading.
For few hours I am struggling with async code in C# and I can't really get why is my code deadlocked.
So far I've red many articles and anything ringed the bell for me.
Hope you can help me.
Here is the code I am trying to run.
Main
Task.Run(async () =>
{
Task<EventDetailed[]> events = getDetailedEvents();
await events;
}).Wait();
getDetailedEvents:
static async Task<EventDetailed[]> getDetailedEvents()
{
...
EventDetailed[] result = await LoadDetailedEventsDetailsAsync(evnts).ConfigureAwait(false);
return result;
}
And the core of my problem.
LoadDetailedEventsDetailsAsync
async static Task<EventDetailed[]> LoadDetailedEventsDetailsAsync(Event[] events)
{
List<EventDetailed> detailed = new List<EventDetailed>();
List<Task<WebResponse>> responses = new List<Task<WebResponse>>();
List<Event> tasksWithStream = new List<Event>();
foreach (Event e in events)
{
var httpWebRequest = (HttpWebRequest)WebRequest.Create("http://...");
... some headers etc ...
e.Stream = httpWebRequest.GetRequestStreamAsync();
e.WebRequest = httpWebRequest;
tasksWithStream.Add(e);
}
foreach (var tsk in tasksWithStream)
{
try {
await tsk.Stream.ConfigureAwait(false);
using (var streamWriter = new StreamWriter(tsk.Stream.Result))
{
streamWriter.Write("...");
streamWriter.Flush();
streamWriter.Close();
}
responses.Add(tsk.WebRequest.GetResponseAsync());
}
catch (Exception ex)
{
Logger.mes("Failed to get event data.");
}
}
foreach (var response in responses)
{
try
{
await response.ConfigureAwait(false);
}
catch (Exception ex)
{
Logger.mes("Failed to get event data.");
continue;
}
parseData.Add(ParseData(response));
}
A couple points:
First, it's important to note that you should almost never call .Wait (or .Result) on an async task - you should use await instead. One of the very few exceptions is in the Main method of a console app. The reason is that if you don't block the main thread, your program will simply exit prematurely.
Second, if you need to make multiple HTTP requests that do not depend on each other (i.e. request B does not need the results of request A), then there are huge performance gains to be had by executing them in parallel. Better still, you are not consuming a thread per request because the calls are asynchronous, i.e. they don't block a thread while waiting for a response, so the same thread can effectively fire off many concurrent requests.
I won't re-write your code, but I will suggest how I'd restructure it:
static void Main(string[] args)
{
// start all async tasks in parallel.
var tasks = GetEvents().Select(GetEventDetailsAsync);
// wait for them all to complete. normally you should use await instead of Wait,
// but you can't because you're in the main method of a console app.
Task.WhenAll(task).Wait();
}
static IEnumerable<Event> GetEvents()
{
// build a list of whatever metadata is needed to do your async work.
// do NOT do any actual async work here.
}
async static Task<EventDetailed> GetEventDetailsAsync(Event e)
{
// do all async work here, use await as needed,
// but only for one event (no loops).
}