检测父任务取消的正确方法是什么?

What is the proper way to detect parent Task cancellation?

我正在开发一个概念验证应用程序,该应用程序使用任务和信号量对数字列表进行分解,目前我有一个任务列表,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;

因子Class代码:

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

释放finally块中的信号量,使其始终正确释放。无需检测取消。

此外,隐藏在日志消息中的副作用也不是好的风格:

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

我是通过文字搜索才找到的。否则永远不会注意到这个错误。

使用取消令牌:

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

我终于找到了我正在寻找的解决方案,它允许我启动(Start())我所有的 Task 对象,运行 它们通过信号量,观察 CancellationToken,然后检测Task是否被取消或正常完成。在这种情况下,如果 TaskCancellationTokenSource.Cancel() 被触发之前进入信号量并开始处理,它只会 "complete normally"。

这个答案:Elegantly handle task cancellation 把我推向了正确的方向。我最终捕获了 OperationCancelledException,记录它,然后重新抛出它,以便在 ContinueWith Task

中进行检查

这是解决我的问题的更新代码

因素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
            {

            }

        }

处理方式:

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