What is the correct way to detect parent cancellation of a task?

I am developing a proof of concept application that defines a list of numbers using tasks and a semaphore, currently I have a List of tasks List<Task>

that accept FactorNumberClass

and then calculate the coefficients of a certain number within FactorNumberClass

this works correctly. With each T task, I have a task ContinueWith

that updates the progress of the total taken into account, the average factoring time and updates the progress bar with the value (Number successfully completed) / (Total numbers to be posted) .When factoring these, Tasks

enter a SemaphoreSlim.Wait(cancelToken)

which limits the current factoring 5 active Tasks

. Finally, I have ContinueWhenAll

one that logs when all tasks are complete. Aside from cancellation, it all works as I expect.

The problem occurs when I try to cancel Tasks, I cannot determine if the task has been canceled and therefore cannot determine exactly whether the number was successfully posted or canceled. How can I tell if the parent task has been canceled or completed?

Defining a cancellation token:

public static CancellationTokenSource tokenSource = new CancellationTokenSource();
public static CancellationToken ct = tokenSource.Token;

      

Factor class code:

public class FactorNumberClass
{
    public FactorNumberClass()
    {
    }

    public FactorNumberClass(int num, int threadnum)
    {
        this.number = num;
        this.threadNumber = threadnum;
    }

    public List<int> factors = new List<int>();
    public int number;
    public int max;
    public int threadNumber;
}

      

Factoring method:

public void Factor(FactorNumberClass F, CancellationToken token)
        {
            LogtoStatusText("Thread: " + F.threadNumber + " Trying to enter semaphore");

            try
            {
                ASemaphore.Wait(ct);

                F.max = (int)Math.Sqrt(F.number);  //round down

                for (int factor = 1; factor <= F.max; ++factor)
                { //test from 1 to the square root, or the int below it, inclusive.
                    if (F.number % factor == 0)
                    {
                        F.factors.Add(factor);
                        if (factor != F.number / factor)
                        {
                            F.factors.Add(F.number / factor);
                        }
                    }
                }

                F.factors.Sort();
                Thread.Sleep(F.number * 300);
                LogtoStatusText("Task: " + F.threadNumber + " Completed - Factors: " + string.Join(",", F.factors.ToArray()));
                LogtoStatusText("Thread: " + F.threadNumber + " Releases semaphore with previous count: " + ASemaphore.Release());
            }
            catch (OperationCanceledException ex)
            {
                LogtoStatusText("Thread: " + F.threadNumber + " Cancelled.");
            }
            finally
            {
            }
        }

      

Method that starts processing:

public void btnStart_Click(object sender, RoutedEventArgs e)
        {
            Task T;
            List<Task> TaskList = new List<Task>();
            LogtoStatusText("**** Begin creating tasks *****");
            s1.Start();

            AProject.FactorClassList.ForEach((f) =>
            {
                T = new Task(((x) => { OnUIThread(() => { RunningTasks++; }); Factor(f, ct); }), ct);

                T.ContinueWith((y) =>
                {
                    if (y.IsCompleted)
                    {
                        AProject.TotalProcessedAccounts++;
                        AProject.AverageProcessTime = (((Double)AProject.TotalProcessedAccounts / s1.ElapsedMilliseconds) * 1000);
                    }
                    OnUIThread(() => { RunningTasks--; });
                    OnUIThread(() => { UpdateCounts(AProject); });
                });

                TaskList.Add(T);
            });

            try
            {
                Task.Factory.ContinueWhenAll(TaskList.ToArray(), (z) => { LogtoStatusText("**** Completed all Tasks *****"); OnUIThread(() => { UpdateCounts(AProject); }); });
            }
            catch (AggregateException a)
            {
                // For demonstration purposes, show the OCE message.
                foreach (var v in a.InnerExceptions)
                    LogtoStatusText("msg: " + v.Message);
            }

            LogtoStatusText("**** All tasks have been initialized, begin processing *****");
            TaskList.ForEach(t => t.Start());
        }

      

+3


source to share


3 answers


I finally found the solution I was looking for, to allow me to run ( Start()

) all my objects Task

, run them through semaphores, watch for CancellationToken

, and then determine if it Task

was canceled or passed ok. In this case, a Task

will only be "finished" if it entered the semaphore and started processing before it was started CancellationTokenSource.Cancel()

.

This answer: Handling task cancellations elegantly nudged me in the right direction. I ended up catching it OperationCancelledException

, running it and then re-throwing it to study it inContinueWith

Task

Here is the updated code that solved my problem

Factor class:



private void Factor(FactorNumberClass F)
        {


            LogtoStatusText("Thread: " + F.threadNumber + " Trying to enter semaphore");

            try
            {
                ASemaphore.Wait(ct);

                F.max = (int)Math.Sqrt(F.number);  //round down

                for (int factor = 1; factor <= F.max; ++factor)
                { //test from 1 to the square root, or the int below it, inclusive.
                    if (F.number % factor == 0)
                    {
                        F.factors.Add(factor);
                        if (factor != F.number / factor)
                        {
                            F.factors.Add(F.number / factor);
                        }
                    }
                }

                F.factors.Sort();

                Thread.Sleep(F.number * 300);

                LogtoStatusText("Task: " + F.threadNumber + " Completed - Factors: " + string.Join(",", F.factors.ToArray()));

                LogtoStatusText("Thread: " + F.threadNumber + " Releases semaphore with previous count: " + ASemaphore.Release());
            }
            catch
            {
                LogtoStatusText("Thread: " + F.threadNumber + " Cancelled");
                throw;

            }
            finally
            {

            }

        }

      

Processing methods:

public void btnStart_Click(object sender, RoutedEventArgs e)
{
    LaunchTasks();
}

private void LaunchTasks()
        {
            Task T;
            List<Task> TaskList = new List<Task>();

            LogtoStatusText("**** Begin creating tasks *****");

            s1.Start();

            AProject.FactorClassList.ForEach((f) =>
            {
                T = new Task(((x) => { OnUIThread(() => { RunningTasks++; }); Factor(f); }), ct);

                T.ContinueWith((y) =>
                {
                    if (y.Exception != null)
                    {
                        // LogtoStatusText(y.Status + " with "+y.Exception.InnerExceptions[0].GetType()+": "+ y.Exception.InnerExceptions[0].Message);
                    }
                    if (!y.IsFaulted)
                    {

                        AProject.TotalProcessedAccounts++;
                        AProject.AverageProcessTime = (((Double)AProject.TotalProcessedAccounts / s1.ElapsedMilliseconds) * 1000);
                    }
                    OnUIThread(() => { RunningTasks--; });
                    OnUIThread(() => { UpdateCounts(AProject); });


                });

                TaskList.Add(T);
            });

            try
            {
                Task.Factory.ContinueWhenAll(TaskList.ToArray(), (z) => { LogtoStatusText("**** Completed all Tasks *****"); OnUIThread(() => { UpdateCounts(AProject); }); });
            }
            catch (AggregateException a)
            {
                // For demonstration purposes, show the OCE message.
                foreach (var v in a.InnerExceptions)
                    LogtoStatusText("msg: " + v.Message);
            }

            LogtoStatusText("**** All tasks have been initialized, begin processing *****");

            TaskList.ForEach(t => t.Start());
        }

      

0


source


Release the semaphore on the block finally

so that it is always properly released. There is no need to detect cancellation.

Also, the side effects hidden in the log messages are not good style:



LogtoStatusText("..." + ASemaphore.Release());

      

I only found this in text search. I would never have noticed a mistake.

+2


source


Using a cancellation token:

using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
    static void Main()
    {
        var tokenSource2 = new CancellationTokenSource();
        CancellationToken ct = tokenSource2.Token;

        var task = Task.Factory.StartNew(() =>
        {

            // Were we already canceled?
            ct.ThrowIfCancellationRequested();

            bool moreToDo = true;
            while (moreToDo)
            {
                // Poll on this property if you have to do 
                // other cleanup before throwing. 
                if (ct.IsCancellationRequested)
                {
                    // Clean up here, then...
                    ct.ThrowIfCancellationRequested();
                }

            }
        }, tokenSource2.Token); // Pass same token to StartNew.

        tokenSource2.Cancel();

        // Just continue on this thread, or Wait/WaitAll with try-catch: 
        try
        {
            task.Wait();
        }
        catch (AggregateException e)
        {
            foreach (var v in e.InnerExceptions)
                Console.WriteLine(e.Message + " " + v.Message);
        }
        finally
        {
            tokenSource2.Dispose();
        }

        Console.ReadKey();
    }
}

      

https://msdn.microsoft.com/en-us/library/dd997396%28v=vs.110%29.aspx

+1


source







All Articles