2

I am using a Task to read some data from the Database etc. Let's assume I can't change the Dataaccess-API/-Layer.

This dataaccess might sometimes need some time (network-traffic etc.). It will be loaded everey time a user changes a selected item or changes the filter that shows a subset of the aviable data.

At the end I have a little example of my Task-Start-Method.

My Question is: If the user changes the filter/selection while the task is still running, how can I stop it from running? When using the cancellation token it will finish (as I am not using a "big" loop in the task, I can't just check every iteration for .IsCancelled.

My idea was to use the return type of the Task to fill the SelectableVolts and check the returning Task on IsCancelled before assigning the new value. But how to do this for an async Task?

// added code at the bottom of this question

UPDATE: After getting comments like "I am not totally sure what your question is asking for, but this should help you get a feel for some avalible options." I will try to clarify my problem a little bit. At least I hope so ;)

  1. User selects object in datagrid
  2. ViewModel needs data and asks method/class/foo for data.
  3. Task(A) is started
  4. Task(A) is still working. User selects different object/row.
  5. Steps 1, 2 and 3 are repeated. So Task(A) should be cancelled/stopped and a new Task(B) starts.
  6. When Task(B) is finished its data should be displayed. In no way Task(A)s data should be made available.

So the question is: How to achieve steps 5 and 6 in a correct way.

Full Code:

    private CancellationToken cancelTokenOfFilterTask;
    private CancellationTokenSource cancelTokenSourceOfFilterTask;

private void FilterVolts()
{
    if (IsApplicationWorking)
    {
        cancelTokenSourceOfFilterTask.Cancel();
    }

    // Prepare CancelToken
    cancelTokenSourceOfFilterTask = new CancellationTokenSource();
    cancelTokenOfFilterTask = cancelTokenSourceOfFilterTask.Token;

    IsApplicationWorking = true;
    if (SelectableVolts != null && SelectableVolts.Count >= 0)
    {
        VoltThatIsSelected = null;
        SelectableVolts.Clear();
    }

    Task voltLoadTask = null;
    voltLoadTask = Task.Factory.StartNew<List<SelectableVoltModel>>(() => {
       VoltsLoader loader = new VoltsLoader();
       Thread.Sleep(2000);
       var listOfVolts = loader.LoadVoltsOnFilter(_sourceOfCachableData, ChosenFilter);
            return listOfVehicles;
        }, cancelTokenOfFilterTask).ContinueWith(listSwitcher =>
        {
            switch (listSwitcher.Status)
            {
                case TaskStatus.RanToCompletion:
                    SelectableVolts = new ObservableCollection<SelectableVoltsModel>(listSwitcher.Result);
                    IsApplicationWorking = false;
                    break;
                case TaskStatus.Canceled:
                    Console.WriteLine("Cancellation seen"); // Gets never called
                    break;           
                default:
                    break;
            }
        });
    }

Somehow when I call this Method more than once, all will run in TaskStatus.RanTocompletion how can this be possible? Am I doing someting wrong with the cancel-token?

Brian
  • 3,850
  • 3
  • 21
  • 37
basti
  • 2,649
  • 3
  • 31
  • 46
  • 1
    Please move your own answer out of the question and into an actual answer below (i.e. answer your own question). Then accept your own answer, if it's what worked for you. – tomfanning Sep 27 '12 at 20:52
  • OK - I just wanted to give @Killercam the apprecation for showing me the right way. But you are right maybe the "real" answer would be found fater when marked as answer. – basti Sep 28 '12 at 07:00

3 Answers3

1

I am not sure why you cannot include the cancellation support you require in your loop. To do this you just pass the cancelTokenOfFilterTask into the method you are calling inside the 'StartNew` delegate. Then inside that method you can do

token.ThrowIfCancellationRequested();

To check whether the Task was completed sucessfully and deal with the relevent outcome, use a continuation

cancelTokenSourceOfFilterTask = new CancellationTokenSource();
cancelTokenOfFilterTask = cancelTokenSourceOfFilterTask.Token;

Task task = null;
task = Task.Factory.StartNew(() =>
{
    VoltLoader vl = new VoltLoader();
    var listOfVolts = vl.LoadVoltsOnFilter(_sourceOfCachableData, ChosenFilter);
    SelectableVolts = new ObservableCollection<SelectableVoltsModel>(listOfVolts);
}, cancelTokenOfFilterTask);

task.ContinueWith(ant => 
{
    switch (ant.Status)
    {
        // Handle any exceptions to prevent UnobservedTaskException.             
        case TaskStatus.RanToCompletion:
            if (asyncTask.Result)
            {
                // Do stuff...
            }
            else
            {
                // Do stuff...
            }
            break;
        case TaskStatus.Canceled:
            // If Cancelled you can start the task again reading the new settings.
            break;
        case TaskStatus.Faulted:
            break;
    }
}, CancellationToken.None, 
   TaskContinuationOptions.None, 
   TaskScheduler.FromCurrentSynchronizationContext());

I am not totally sure what your question is asking for, but this should help you get a feel for some avalible options.

I hope this helps.

MoonKnight
  • 23,214
  • 40
  • 145
  • 277
  • The idea with the ContinueWith looks very promising. How would I use this in a method? So I call the method `loadNeededData` everytime the user selects another row in a datagrid. When the user changes the row _before_ the old task is finished I need it cancelled. Will every Task run in the same ContiuneWith so I can simply use your switch-statement?? – basti Sep 26 '12 at 10:49
  • you don't need to check token.IsCancellationRequested before calling `token.ThrowIfCancellationRequested()` as the method is suppose to raise an exception only `token.IsCancellationRequested` is `true`, the code you wrote is equal to checking the same `token.IsCancellationRequested` twice and then throwing an exception – Vamsi Sep 26 '12 at 11:13
  • See edit... Of course it is a error. – MoonKnight Sep 26 '12 at 11:40
  • You will have to create some logical that you set in the continuation upon a cancellation. Then recall the method that you invoked in the first place... – MoonKnight Sep 26 '12 at 11:41
  • @Killercam now after the edit it is plain wrong, the `token.ThrowIfCancellationRequested();` will never work, the code you wrote is equivalent to the following code snippet `if (!token.IsCancellationRequested) { if (token.IsCancellationRequested) { throw new OperationCanceledException(token); } }` how is that correct ? – Vamsi Sep 27 '12 at 05:53
  • Hahaha... What am I doing... Sorry. You are right, I was rushing and on a phone. I will fix this up. Thanks. – MoonKnight Sep 27 '12 at 08:09
1

Answer: I think I found a solution, I have to reset the CancellationTokenSource after every cancel-request.

Here is my updated Code based on the accepted answer. If there's something wrong I am happy about any information :)

    private CancellationToken cancelTokenOfFilterTask;
    private CancellationTokenSource cancelTokenSourceOfFilterTask = new CancellationTokenSource();

    private void FilterVolts()
    {
        if (IsApplicationWorking)
        {
            cancelTokenSourceOfFilterTask.Cancel();
        }

        // Prepare CancelToken
        cancelTokenOfFilterTask = cancelTokenSourceOfFilterTask.Token;

        IsApplicationWorking = true;
        if (SelectableVolts != null && SelectableVolts.Count >= 0)
        {
            VoltThatIsSelected = null;
            SelectableVolts.Clear();
        }

        Task voltsLoadTask = null;
        voltsLoadTask = Task.Factory.StartNew<List<SelectableVoltsModel>>(() => {
            VehicleLoader loader = new VehicleLoader();
            Thread.Sleep(2000); // just for testing, so the task runs a "long" time
            var listOfVolts = loader.LoadVoltsOnFilter(_sourceOfCachableData, ChosenFilter);
            return listOfVolts;
        }, cancelTokenOfFilterTask).ContinueWith(listSwitcher =>
        {
            switch (listSwitcher.Status)
            {
                case TaskStatus.RanToCompletion:
                    SelectableVolts = new ObservableCollection<SelectableVoltModel>(listSwitcher.Result);
                    IsApplicationWorking = false;
                    break;
                case TaskStatus.Canceled:
                    cancelTokenSourceOfFilterTask = new CancellationTokenSource(); // reset Source
                    break;
                default:
                    break;
            }
        });
    }
basti
  • 2,649
  • 3
  • 31
  • 46
0

one way would be to use the CancellationToken.ThrowIfCancellationRequested Method which when called will throw an OperationCanceledException if the token had a cancellation request with it

you can use it like this

    var cancelTokenSourceOfFilterTask = new CancellationTokenSource();
    var cancelTokenOfFilterTask = cancelTokenSourceOfFilterTask.Token;

    Task.Factory.StartNew(() =>
    {
        VoltLoader vl = new VoltLoader();

        //if a request is raised for cancellation then an exception will be thrown
        cancelTokenOfFilterTask.ThrowIfCancellationRequested();

        var listOfVolts = vl.LoadVoltsOnFilter(_sourceOfCachableData, ChosenFilter);

        SelectableVolts = new ObservableCollection<SelectableVoltsModel>(listOfVolts);
    }, cancelTokenOfFilterTask);

the above code is same as the following code snippet

    var cancelTokenSourceOfFilterTask = new CancellationTokenSource();
    var cancelTokenOfFilterTask = cancelTokenSourceOfFilterTask.Token;

    Task.Factory.StartNew(() =>
    {
        VoltLoader vl = new VoltLoader();

        //if a request is raised for cancellation then an exception will be thrown
        if (cancelTokenOfFilterTask.IsCancellationRequested) 
           throw new OperationCanceledException(cancelTokenOfFilterTask);

        var listOfVolts = vl.LoadVoltsOnFilter(_sourceOfCachableData, ChosenFilter);

        SelectableVolts = new ObservableCollection<SelectableVoltsModel>(listOfVolts);
    }, cancelTokenOfFilterTask);

the above is just an example of how it can be used, for more this you can read this SO question about why Cancellation token is required in Task constructor

Community
  • 1
  • 1
Vamsi
  • 4,237
  • 7
  • 49
  • 74
  • But will it actually be called if the cancellation follows after the `if-statement`? So its cancelled after `vl.LoadVoltsOnFilter`? – basti Sep 26 '12 at 07:51
  • no, it won't, my advice would be to use that before any heavy operation, you can also same snippet multiple times if you need it – Vamsi Sep 26 '12 at 07:57
  • AFAIK this will not work in all cases. If the Task was cancelled it _can_ happen, that the Task finishes before the last if(isCancelled) returns false. A bad timed context-switch in the parallel execution would be sufficent for this... – basti Sep 26 '12 at 10:51
  • yes, that's what i said in my last comment – Vamsi Sep 26 '12 at 11:10