Как правильно определить отмену родительской задачи?

Я разрабатываю приложение для проверки концепции, которое факторизует список чисел, используя задачи и семафор, в настоящее время у меня есть список задач, List<Task>, которые принимают FactorNumberClass, а затем вычисляют факторы определенного числа в FactorNumberClass, в настоящее время это работает правильно . С каждой задачей T у меня есть задача ContinueWith, которая обновляет ход выполнения факторизации общего числа, среднее время для факторизации и обновляет индикатор выполнения со значением (Числа успешно факторизованы)/(Общее число для факторинга). При факторинге этих Tasks введите SemaphoreSlim.Wait(cancelToken), который ограничивает текущий факторинг до 5 активных Tasks. Наконец, у меня есть ContinueWhenAll, который регистрирует завершение всех задач. Если не отменить, все работает так, как я задумал.

Проблема возникает, когда я пытаюсь отменить задачи, я не могу определить, была ли задача отменена, и поэтому не могу точно определить, было ли число успешно учтено или оно было отменено. Как я могу определить, была ли родительская задача отменена или завершена?

Определение токена отмены:

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

Код класса фактора:

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;
}

Метод факторинга:

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
            {
            }
        }

Метод, запускающий обработку:

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());
        }

person Pseudonym    schedule 07.05.2015    source источник


Ответы (3)


Освободите семафор в блоке finally, чтобы он всегда освобождался должным образом. Нет необходимости обнаруживать отмену.

Кроме того, побочные эффекты, спрятанные в сообщениях журнала, не являются хорошим стилем:

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

Я нашел это только через текстовый поиск. Иначе никогда бы не заметил ошибку.

person usr    schedule 07.05.2015
comment
Хороший улов, LogtoStatusText — это просто вывод для просмотра того, что, черт возьми, происходит с приложением, но вы совершенно правы. - person Pseudonym; 07.05.2015
comment
Кроме того, если была запрошена отмена, я не хочу, чтобы существующие ожидающие задачи входили в семафор, что они и сделают, если я не использую Wait(cancellationToken) - person Pseudonym; 07.05.2015
comment
Я понимаю, что этот ответ может на самом деле не помочь вам решить проблему. Подумайте о том, чтобы исключить исключение отмены из Factor с помощью throw;. Таким образом, отмена распространяется. Кроме того, рассмотрите возможность использования await в нескольких местах для упрощения логики. Некоторые вещи слышны для чтения прямо сейчас. await особенно хорош для автоматического перехода к потоку пользовательского интерфейса. - person usr; 07.05.2015
comment
В дополнение к тому, что использование finally для освобождения семафора будет освобождать его каждый раз, даже после того, как токен отмены (ct) вызовет событие отмены, это в конечном итоге увеличит количество семафоров до максимального значения. - person Pseudonym; 07.05.2015
comment
Не могу использовать await. Я застрял в VS2010 и .NET 4.0. - person Pseudonym; 07.05.2015

Использование токена отмены:

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

person Oscar    schedule 07.05.2015
comment
Как это связано с семафором и ContinueWith? Я видел эту статью раньше, но я не думаю, что она касается того, что я ищу - person Pseudonym; 07.05.2015

Я наконец нашел решение, которое искал, которое позволило бы мне запускать (Start()) все мои объекты Task, запускать их через семафорезлим, наблюдать за CancellationToken, а затем определять, был ли Task отменен или завершился нормально. В этом случае Task будет "нормально завершаться" только в том случае, если он войдет в семафор и начнет обработку до того, как будет запущен CancellationTokenSource.Cancel().

Этот ответ: Элегантная обработка отмены задачи подтолкнул меня в правильном направлении. В итоге я поймал OperationCancelledException, зарегистрировал его, а затем повторно бросил для проверки в рамках ContinueWith Task.

Вот обновленный код, который решил мою проблему

Класс фактора:

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
            {

            }

        }

Методы обработки:

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());
        }
person Pseudonym    schedule 07.05.2015