Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to reset the CancellationTokenSource and debug the multithread with VS2010?

Tags:

I have used CancellationTokenSource to provide a function so that the user can cancel the lengthy action. However, after the user applies the first cancellation, the later further action doesn't work anymore. My guess is that the status of CancellationTokenSource has been set to Cancel and I want to know how to reset it back.

  • Question 1: How to reset the CancellationTokenSource after the first time usage?

  • Question 2: How to debug the multithread in VS2010? If I run the application in debug mode, I can see the following exception for the statement

    this.Text = string.Format("Processing {0} on thread {1}", filename, Thread.CurrentThread.ManagedThreadId); 

InvalidOperaationException was unhandled by user code Cross-thread operation not valid: Control 'MainForm' accessed from a thread other than the thread it was created on.

Thank you.

private CancellationTokenSource cancelToken = new CancellationTokenSource();  private void button1_Click(object sender, EventArgs e) {     Task.Factory.StartNew( () =>     {         ProcessFilesThree();     }); }  private void ProcessFilesThree() {     ParallelOptions parOpts = new ParallelOptions();     parOpts.CancellationToken = cancelToken.Token;     parOpts.MaxDegreeOfParallelism = System.Environment.ProcessorCount;      string[] files = Directory.GetFiles(@"C:\temp\In", "*.jpg", SearchOption.AllDirectories);     string newDir = @"C:\temp\Out\";     Directory.CreateDirectory(newDir);      try     {         Parallel.ForEach(files, parOpts, (currentFile) =>         {             parOpts.CancellationToken.ThrowIfCancellationRequested();              string filename = Path.GetFileName(currentFile);              using (Bitmap bitmap = new Bitmap(currentFile))             {                 bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone);                 bitmap.Save(Path.Combine(newDir, filename));                 this.Text =  tring.Format("Processing {0} on thread {1}",  filename, Thread.CurrentThread.ManagedThreadId);             }         });          this.Text = "All done!";     }     catch (OperationCanceledException ex)     {         this.Text = ex.Message;                                  } }  private void button2_Click(object sender, EventArgs e) {     cancelToken.Cancel(); } 
like image 749
q0987 Avatar asked May 29 '11 15:05

q0987


People also ask

Can I reuse Cancellationtokensource?

Therefore, cancellation tokens cannot be reused after they have been canceled. If you require an object cancellation mechanism, you can base it on the operation cancellation mechanism by calling the CancellationToken. Register method, as shown in the following example.

Is Cancellationtokensource thread safe?

Cancellation tokens are generally thread safe by design so passing them between threads and checking them should not be a problem.


2 Answers

Question 1> How to reset the CancellationTokenSource after the first time usage?

If you cancel it, then it's cancelled and can't be restored. You need a new CancellationTokenSource. A CancellationTokenSource isn't some kind of factory. It's just the owner of a single token. IMO it should have been called CancellationTokenOwner.

Question 2> How to debug the multithread in VS2010? If I run the application in debug mode, I can see the following exception for the statement

That has nothing to do with debugging. You can't access a gui control from another thread. You need to use Invoke for that. I guess you see the problem only in debug mode because some checks are disabled in release mode. But the bug is still there.

Parallel.ForEach(files, parOpts, (currentFile) => {   ...     this.Text =  ...;// <- this assignment is illegal   ... }); 
like image 193
CodesInChaos Avatar answered Oct 23 '22 23:10

CodesInChaos


Under Debug > windows in visual studio there you'll want to look at the threads window, the callstack window and the paralell tasks window.

When the debugger breaks for the exception you're getting, you can look at the callstack window to see what thread is making the call and from where that thread is coming from.

-edit based on posted screenshot-

you can right click in the call stack and select 'show external code' to see exactly what is going on in the stack, but 'external code' means 'somewhere in the framework' so it may or may not be useful (i usually find it interesting though :) )

From your screenshot we can also see that the call is beeing made from a thread pool thread. If you look at the threads window, you'll see one of them has a yellow arrow. Thats the thread we're currently executing on and where the exception is beeing thrown. The name of this thread is 'Worker Thread' and that means its coming from the thread pool.

As has already been noted you must make any updates to your ui from the user thread. You can for example use the ´Invoke´ on the control for this, see @CodeInChaos awnser.

-edit2-

I read through your comments on @CodeInChaos awnser and here is one way to do it in a more TPL like way: First of all you need to get hold of an instance of a TaskScheduler that will run tasks on the UI thread. you can do this by declaring a TaskScheduler in you ui-class named for example uiScheduler and in the constructor setting it to TaskScheduler.FromCurrentSynchronizationContext();

Now that you have it, you can make a new task that updates the ui:

 Task.Factory.StartNew( ()=> String.Format("Processing {0} on thread {1}", filename,Thread.CurrentThread.ManagedThreadId),  CancellationToken.None,  TaskCreationOptions.None,  uiScheduler ); //passing in our uiScheduler here will cause this task to run on the ui thread 

Note that we pass the task scheduler to the task when we start it.

There is also a second way to do this, that uses the TaskContinuation apis. However we cant use Paralell.Foreach anymore, but we'll use a regular foreach and tasks. the key is that a task allows you to schedule another task that will run once the first task is done. But the second task does not have to run on the same scheduler and that is very useful for us right now since we want to do some work in the background and then update the ui:

  foreach( var currectFile in files ) {     Task.Factory.StartNew( cf => {        string filename = Path.GetFileName( cf ); //make suse you use cf here, otherwise you'll get a race condition       using( Bitmap bitmap = new Bitmap( cf ) ) {// again use cf, not currentFile         bitmap.RotateFlip( RotateFlipType.Rotate180FlipNone );         bitmap.Save( Path.Combine( newDir, filename ) );         return string.Format( "Processed {0} on thread {1}", filename, Thread.CurrentThread.ManagedThreadId );       }     }, currectFile, cancelToken.Token ) //we pass in currentFile to the task we're starting so that each task has their own 'currentFile' value     .ContinueWith( t => this.Text = t.Result, //here we update the ui, now on the ui thread..                    cancelToken.Token,                     TaskContinuationOptions.None,                     uiScheduler ); //..because we use the uiScheduler here   } 

What we're doing here is making a new task each loop that will do the work and generate the message, then we're hooking on another task that will actually update the ui.

You can read more about ContinueWith and continuations here

like image 39
aL3891 Avatar answered Oct 23 '22 23:10

aL3891