locked
cancellation_token not working RRS feed

  • Question

  • hello,

    i am having problems when using cancellation_token_source with create_async. the task isn´t cancelled...

    i have the following code.

    concurrency::cancellation_token_source cts;
    
    ...
    ...
    
    IAsyncOperationWithProgress<String^, double>^ Remote_Visual_DF__Linux_::MainPage::execAsync( wstring host, wstring port, wstring user, wstring pass, const wchar_t * cmd, cancellation_token_source &cts ) 
    { 
    	auto token = cts.get_token();
    	return create_async([this, host, port, user, pass, cmd, token ](progress_reporter<double> reporter, concurrency::cancellation_token token ) -> String^
        {
    		if ( token.is_canceled() ) {
    			cancel_current_task();
    		}
    
    		while(! token.is_canceled() ) {
    ...
    ...
    }		
    catch (Exception^ ex)
    		   {
    			   err("An exception occured!");
    		 }
    	}
    	});
    }

    I am trying to cancel the operation with "cts.canel()" but the task never gets cancelled. I hope someone can give me a hint to fix this error.

    Thanks a lot for your help!


    Monday, April 29, 2013 7:00 PM

Answers

  • When you say "the task isn't canceled", what do you mean here:

    • No matter where you check token.is_canceled() (or a variety of other ways of checking), you get false/no indication of cancellation
    • or) Your completion delegate which you've attached gets AsyncStatus::Completed...?

    Throw a few breakpoints on and look at where the task is (and what it's doing/how it completes) when/after you execute your _cts.cancel() on the UI thread.  That should make it much more obvious what's actually going on.

    As to create_task vs. create_async, the only time you need to use create_async is if you want to take the asynchronous operation and expose it across the ABI boundary to another WinRT component or another language (e.g.: it's returned from a public method of a ref class, etc...).  Internally, create_async is creating a task and sticking a wrapper around it to make it a WinRT IAsync* object.  While you can certainly do a variety of other things, the general recommendation is to stay with task<T> if you're just using this as internal implementation within a C++ component.

    On the asyncop->Cancel question: there are a variety of ways you could do this -- from just about to any synchronization mechanism to simply if(asyncOp != nullptr) depending on the rest of your code.

    • Marked as answer by dotlike.net Sunday, May 5, 2013 9:40 AM
    Friday, May 3, 2013 6:25 PM

All replies

  • The main problem here is that you have conflicting names for token.  It's both a capture to the lambda and a parameter of the lambda ([..., token](... token) { }).  The parameter name wins here...  That token is one which is created internally by the runtime and will only be flagged as canceled when the IAsyncOperationWithProgress which gets returned by create_async ends up canceled.

    If that doesn't address your concern, reply back to the thread and I'll try to help further.

    Monday, April 29, 2013 10:49 PM
  • can you give me a code example for fixing this issue and cancelling the task from outsite the lambda function?

    thanks for your response!

    Tuesday, April 30, 2013 8:28 PM
  • For create_async, the IAsync* which is returned from create_async has its cancellation automatically hooked up to the token which is passed to the lambda.  For instance:

    auto op = create_async( [](cancellation_token token){
        ...
        while(!token.is_cancelled())
        {
            ...
        }
        ...
    });

    You'd break out of the while loop due to cancellation if you made a cancellation call on the returned op:

    op->Cancel();

    You don't need to explicitly create a cancellation_token_source for this to happen.  Effectively, the runtime is creating one for you on create_async, passing a token to the source into your create_async lambda, and subsequently canceling the source if the Cancel method is ever called on the returned IAsync* object.

    Your code example had the problem of having:

    create_async( [... token ...](..., cancellation_token token){
    });

    The token in the lambda scope is the inpassed one from the runtime and has absolutely nothing to do with the outer cts and captured token.  Unless you are trying to do something unusual, you don't even need the outer cancellation_token_source here...

    Does this clarify...?

    Tuesday, April 30, 2013 9:37 PM
  • thanks for your reply. i fixed the code now like this...

    // cancellation token concurrency::cancellation_token_source cts; .. .. ..

    auto ct = cts.get_token(); auto asyncop = Remote_Visual_DF__Linux_::MainPage::execAsync( passvault.get_host(n), passvault.get_port(n), passvault.get_user(n), passvault.get_pass(n), com, ct ); asyncop->Progress = ref new AsyncOperationProgressHandler<String^, double>( .. .. .. .. IAsyncOperationWithProgress<String^, double>^ Remote_Visual_DF__Linux_::MainPage::execAsync( wstring host, wstring port, wstring user, wstring pass, const wchar_t * cmd, cancellation_token ct ) { //auto token = cts.get_token(); return create_async([this, host, port, user, pass, cmd, ct ](progress_reporter<double> reporter) -> String^ //concurrency::cancellation_token token { if ( ct.is_canceled() ) { cancel_current_task(); throw; }

    i want to execute ct.cancel() if the selection of a ListView changes. But with this code the task is canceled immediately before it is running... 

    thanks for your helpfull replies. i hope you can give me one more hint ;) thanks!!

    Tuesday, April 30, 2013 9:56 PM
  • i also tried now to do the following...

    "test" is a cancellation_token_source.

    	return create_async([this, host, port, user, pass, cmd, ctoken ](progress_reporter<double> reporter, concurrency::cancellation_token token) -> String^ //concurrency::cancellation_token token 
        {
    		test = cancellation_token_source::create_linked_source( token );
    
    		if ( token.is_canceled() ) {
    			cancel_current_task();
    		}

    but it never gets cancelled...

    what would be the preferred way to cancel a create_async task from another function like one which is executes when the ListView selection changes?

    THANKS!

    Wednesday, May 1, 2013 9:44 AM
  • One usually calls create_async to make an asynchronous operation that can cross the ABI boundary to another WinRT component or another language.  The usual pattern here is that you call the Cancel method on the IAsync* which is returned from create_async:

    auto op = create_async( [](cancellation_token token){
        if (token.is_canceled()) // a number of ways to check this
        {
            ...
        }
    });
    
    ...
    
    op->Cancel();

    The other usual pattern -- for tasks rather than async constructs created via create_async -- is that you simply create a task with which you associate a token from a token source and cancel the source:

    cancellation_token_source src;
    auto token = src.get_token();
    
    task<void> t( [token](){
        // One way to check...
        if (token.is_canceled())
        {
        }
    
        // Another way to check...
        if (is_task_cancellation_requested())
        {
        }
    }, token);
    
    ...
    
    src.cancel();

    Your example of using create_linked_source doesn't really add anything.  That function basically says return me a new cancellation_token_source that gets canceled if the token I created this linked source from is ever canceled.

    Note that I say that these are the usual patterns of cancellation.  There are certainly a variety of other ways that things can be structured to achieve similar functionality.

    Wednesday, May 1, 2013 5:45 PM
  • if i would use op->Cancel() ... i get a segfault if no task is running and i call this method...

    As i said i want to call this method when the selection of a ListView is changed. So if i call op->Cancel() and no task is running my code segfaults...

    when i use src.cancel() my problem is that every task is canceled right at the beginning because "src" is set to cancel. how can i reset this cancellation_token_source.

    is there a way to reset the cancellation_token_source after the task was canceled. 

    maybe I am not getting something ;) ...

    Wednesday, May 1, 2013 6:51 PM
  • i am using the following code

    auto asyncop  = Remote_Visual_DF__Linux_::MainPage::execAsync( passvault.get_host(n), passvault.get_port(n), passvault.get_user(n), passvault.get_pass(n), com, ct );

    in a separate function. How can i cancel the create_async-task which is created by the function execAsync from another function without using a cancellation_token_source and only using asyncop->Cancel() ?

    from my understand the preferred way would be to use a cancellation_token_source which I am calling every time the ListView-Selection changes to cancel already running tasks. 

    As i mentioned in my previous post if I use the canellation_token_source and use cts.cancel() when the ListView-Selection changes the created tasks are immediately caneled...

    		auto asyncop  = Remote_Visual_DF__Linux_::MainPage::execAsync( passvault.get_host(n), passvault.get_port(n), passvault.get_user(n), passvault.get_pass(n), com;
    
    ...
    ...
    IAsyncOperationWithProgress<String^, double>^ Remote_Visual_DF__Linux_::MainPage::execAsync( wstring host, wstring port, wstring user, wstring pass, const wchar_t * cmd) 
    { 
    	return create_async([this, host, port, user, pass, cmd](progress_reporter<double> reporter, concurrency::cancellation_token token) -> String^ //concurrency::cancellation_token token 
        {
    		token = cts.get_token();
    
    		if ( token.is_canceled() ) {
    			cancel_current_task();
    		}

    using the above code my task is never executed instead it is canceled immediately. is there a way to reset the cancellation_token_source cts after i used it to cancel the task?

    thanks very much. sry for being so annoying...


    Wednesday, May 1, 2013 7:17 PM
  • No problem.  My apologies that I haven't clearly gotten to what you're trying to understand.

    There is no way to "reset" a cancellation_token_source.  Once it's set, it's set.  And yes, a pattern such as the following:

    cancellation_token_source src;
    auto token = src.get_token();
    src.cancel();
    
    task<void> t( [](){ ... }, token);
    

    Will cause the task t to be canceled before running.  In your case, you'll hit the token.is_canceled() and call cancel_current_task() immediately which will effectively cancel right at the start of the task.

    I don't completely know how your code is structured.  There are a number of ways to accomplish what I think you're looking for...  If I'm misunderstanding here, please try to clarify...

    If you've got a cancellation token which is a member of a class which multiple async things are sharing and you cancel this on changes, you can certainly do something like:

    // With whatever appropriate synchronization is needed in your context... 
    //
    // Cancel the old source (and tasks associated with it) and make a new one.
    _cts.cancel();
    _cts = cancellation_token_source();

    New tasks created after your selection change would use a token from the newly created (and not yet canceled) source. You could also do something where you're storing the async op and calling Cancel on it when you hit this scenario.  Which is appropriate probably depends on other aspects of your code.
    Wednesday, May 1, 2013 8:11 PM
  • this solutions looks fine for me but if i use 

    _cts.cancel();
    _cts = cancellation_token_source();

    and later 

    	return create_async([this, host, port, user, pass, cmd ](progress_reporter<double> reporter, concurrency::cancellation_token token) -> String^ //concurrency::cancellation_token token 
        {
    		//ctoken = cancellation_token_source::create_linked_source( token );
    		token = _cts.get_token();
    
    		if ( token.is_canceled() ) {
    			cancel_current_task();
    		}

    the task isn´t cancelled. if I remove the line "_cts = cancellation_token_source();" then the task is always cancelled.

    can you tell me the reason for that?? thanks

    Wednesday, May 1, 2013 8:48 PM
  • hello. my functions look like this atm

    void Remote_Visual_DF__Linux_::MainPage::ButtonCommand_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
    {
    ...
    	wchar_t *com= L"";
    	progress_text->Text = "";
    	err_clear(); // clear previous error messages
    
    	// cancel async-action if other connection is selected
    	_cts.cancel();
    
    	// create a new cancellation token source
    	_cts = cancellation_token_source();
    
    ...
    
    	if ( ListView_Connections->SelectedIndex == -1) { // if no connection profile is selected
    		MsgErr_SelErr->IsOpen = true;
    	} else {
    		int n = passvault.search( ListView_Connections->SelectedItems->GetAt(0)->ToString() );
    
    		
    		auto asyncop  = Remote_Visual_DF__Linux_::MainPage::execAsync( passvault.get_host(n), passvault.get_port(n), passvault.get_user(n), passvault.get_pass(n), com );
    
    			asyncop->Progress = ref new AsyncOperationProgressHandler<String^, double>(
    			[this](Windows::Foundation::IAsyncOperationWithProgress<String^, double>^ asyncInfo, double percent)
    			{
    				progress_text->Text = "Working...";
    				pgbar->Value = percent;		
    			} );
    			
    			asyncop->Completed = ref new AsyncOperationWithProgressCompletedHandler<String^, double>(
    			[this](Windows::Foundation::IAsyncOperationWithProgress<String^,double>^ asyncInfo, AsyncStatus status)
    			{
    				if(asyncInfo->Status == AsyncStatus::Error)
                    {
                        progress_text->Text = err();
                    }
                    else if(asyncInfo->Status == AsyncStatus::Canceled)
                    {
                        progress_text->Text = "Canceled!";
    					err_clear();
                    }
                    else
                    {
                        // operation success
                        progress_text->Text = err();
    					if( err()->IsEmpty() ) {
    						progress_text->Text = "parsing command line output";
    					}
    				}
    			} );
    	}
    }

    and here is the Async-method ...

    IAsyncOperationWithProgress<String^, double>^ Remote_Visual_DF__Linux_::MainPage::execAsync( wstring &host, wstring &port, wstring &user, wstring &pass, const wchar_t * cmd) 
    { 
    	return create_async([this, host, port, user, pass, cmd ](progress_reporter<double> reporter, concurrency::cancellation_token token) -> String^ //concurrency::cancellation_token token 
        {
    		token = _cts.get_token();
    		
    		if ( token.is_canceled() ) {
    			_cts = cancellation_token_source();
    			cancel_current_task();
    		}
    		while ( !token.is_canceled() ) {
    			try {
    ...
    ...
    				return results;
    			}				 
    
    		catch (Exception^ ex) {
    			   err("An exception occured!");
    		 }
    		}
    	});
    }

    but for some reason the tasks are not cancelled when "ButtonCommand_Click" is executed...

    I am really stuck on this as you can see ;)

    I hope you have again some helpful hint for me. Thanks for your time!


    At the moment the task which should be cancelled is executed till if finishes and afterwards the new task is executed...
    Thursday, May 2, 2013 6:53 PM
  • There are a number of things here:

    If by "the tasks are not canceled" you mean that you do not see AsyncStatus::Canceled upon the completion callback, understand that you will only see this if the task in question appropriately responded to cancellation.  I don't know what's in your "..." but if the task runs to the end after checking token.is_canceled() in your loop and returns a value (or returns void successfully), the task is assumed completed and not canceled and will fire off completion in that way.  At the top of your lambda when you check and subsequently call cancel_current_task(); you are acknowledging cancellation and the async operation will appear canceled.

    There's a race condition inherent in the way you've passed the token around.  If you are going to structure code in this way (I'll get to that in a moment), it's probably better to do something like:

    auto token = _cts.get_token();
    return create_async([...token...](...){ ... });

    Otherwise, your async construct may well look at the new source (_cts) to get the token well after the UI thread has done the _cts = cancellation_token_source();.

    A few things I'll point out in general:

    It's a bit weird in this function to have the lambda take a cancellation_token and then overwrite it in the manner in which you do. Having the lambda take a cancellation_token tells the runtime:

    • I'd like the returned async operation to be cancelable.
    • Please pass me a cancellation token which will be set to canceled when the client calls op->Cancel()

    After asking for this, the code throws away the information and does a side-band method of canceling through a different token (e.g.: it never checks the token that the runtime passed). 

    I'll also reiterate that within the bounds of a single component like I assume this is from your examples, create_async isn't necessary.  It's entirely appropriate (and the more common/efficient pattern) to do something like the below:

    //
    // Body of the ExecAsync method:
    //
    auto token = _cts.get_token();
    
    auto execTask = create_task( [token](){
        ... 
        // Your async stuff
        ...
    }, token);
    
    return execTask;
    
    ...
    
    //
    // Client of the ExecAsync method:
    //
    ExecAsync(...).then( [](T result){
        //
        // Use results.  No need to hook up separate completion handler, etc...
        //
    });
    
    Hope that helps!
    Thursday, May 2, 2013 8:16 PM
  • hello. 

    thanks for your response. I am using create_async because the result of the Async-method will be used in the UI directly and is a String^ ... As far as I know i couldn´t use create_task for this...

    i tried to change the code after reading your helpful response like this ...

    IAsyncOperationWithProgress<String^, double>^ Remote_Visual_DF__Linux_::MainPage::execAsync( wstring &host, wstring &port, wstring &user, wstring &pass, const wchar_t * cmd) 
    { 
    	auto token = _cts.get_token();
    
    	return create_async([this, host, port, user, pass, cmd, token ](progress_reporter<double> reporter) -> String^ //concurrency::cancellation_token token 
        {
    		if ( token.is_canceled() ) {
    			ssh.Disconnect();
    			cancel_current_task();
    		}

    but the task isn ´t cancelled ...

    The "async stuff" is a ssh-connection to a remote host and is executing a command and returning a String^ with the output of the command... This works without problems...

    Is there a way to call asyncop->Cancel() only if there is a running taks. This would be the easiest way for me to fix my problems. My problem is that I can´t run asyncop->Cancel when the ListView-selection changes because the program segfaults if asyncop is not defined.

    If I understand you correctly then the above could should work... the race condition shouldn´t be there anymore. Or do you see another error?

    thanks, thanks!

    Thursday, May 2, 2013 9:00 PM
  • When you say "the task isn't canceled", what do you mean here:

    • No matter where you check token.is_canceled() (or a variety of other ways of checking), you get false/no indication of cancellation
    • or) Your completion delegate which you've attached gets AsyncStatus::Completed...?

    Throw a few breakpoints on and look at where the task is (and what it's doing/how it completes) when/after you execute your _cts.cancel() on the UI thread.  That should make it much more obvious what's actually going on.

    As to create_task vs. create_async, the only time you need to use create_async is if you want to take the asynchronous operation and expose it across the ABI boundary to another WinRT component or another language (e.g.: it's returned from a public method of a ref class, etc...).  Internally, create_async is creating a task and sticking a wrapper around it to make it a WinRT IAsync* object.  While you can certainly do a variety of other things, the general recommendation is to stay with task<T> if you're just using this as internal implementation within a C++ component.

    On the asyncop->Cancel question: there are a variety of ways you could do this -- from just about to any synchronization mechanism to simply if(asyncOp != nullptr) depending on the rest of your code.

    • Marked as answer by dotlike.net Sunday, May 5, 2013 9:40 AM
    Friday, May 3, 2013 6:25 PM
  • thanks a lot four your help!

    I set several breakpoints and have done some debugging now. The reason for the task which is not cancelled is, that the ssh-connection was waiting for a timeout. the task was cancelled but the ssh-connection-method was blocking the cancellation of the task.

    your replies were very informational and helpful for me. thanks a lot!

    Sunday, May 5, 2013 9:40 AM