按时间取消任务

And*_*nka 5 c# multithreading task cancellationtokensource

我有一个多线程应用程序,我需要在一定时间后取消每个任务,即使在取消时,他们使用非托管资源.现在我使用以下代码(例如,控制台应用程序).在实际应用中,延迟可能发生在非托管资源中.

static void Main()
    {
        for (int i = 0; i < 10; i++)
        {
            Task.Factory.StartNew(Do, TaskCreationOptions.LongRunning);
        }

        Console.ReadLine();
    }

    private static void Do()
    {
        new Timer(Thread.CurrentThread.Abort, null, 1000, -1);

        try
        {
            Console.WriteLine("Start " + Task.CurrentId);
            Thread.Sleep(2000);
            Console.WriteLine("End " + Task.CurrentId);
        }
        catch (Exception)
        {
            Console.WriteLine("Thread Aborted " + Task.CurrentId);
        }
    }
Run Code Online (Sandbox Code Playgroud)

得到结果:

在此输入图像描述

但从安全角度来看,我不确定它是否适合真正的应用.我也在不同的变种中使用了CancellationToken,但是它没有给我正确的结果,因为我在使用CancellAfter()或.Delay()时使用了时间跨度并且在获得时间后取消任务我获得了以下结果:

static void Main()
    {
        for (int i = 0; i < 10; i++)
        {
            var clt = new CancellationTokenSource();

            Task task = new Task(() =>
            {
                Task.Delay(2000).ContinueWith(_ =>
                {
                    clt.Cancel();

                }, clt.Token);

                Do(clt.Token);

            }, clt.Token);

            task.Start();
        }

        Console.ReadLine();
    }

    private static void Do(CancellationToken cltToken)
    {
        Console.WriteLine("Start " + Task.CurrentId);

        Thread.Sleep(2500);

        if (!cltToken.IsCancellationRequested)
        {
            Console.WriteLine("End " + Task.CurrentId);
        }
        else
        {
            Console.WriteLine("Cancelled "+ Task.CurrentId);
        }
    }
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

在这种情况下,必须取消所有任务,因为Thread.Sleep()>的时间分配用于执行每个任务.但是我们可以看到有些时候要执行.

我也使用以下结构并给出相同的结果:

        static void Main()
    {
        for (int i = 0; i < 10; i++)
        {
            var clt = new CancellationTokenSource();
            clt.CancelAfter(2000);

            Task.Factory.StartNew(Do, clt.Token);

        }

        Console.ReadLine();
    }

    private static void Do(object obj)
    {
        var cltToken = (CancellationToken) obj;

        Console.WriteLine("Start " + Task.CurrentId);

        Thread.Sleep(2500);

        if (!cltToken.IsCancellationRequested)
        {
            Console.WriteLine("End " + Task.CurrentId);
        }
        else
        {
            Console.WriteLine("Cancelled "+ Task.CurrentId);
        }
    }
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

我也使用Parallel并初始化取消令牌内部方法Do(),并使用Timer在时间跨度后取消令牌,但都给出相同的结果.

那么,为什么会发生这种情况,以及在一定时间后取消任务的正确方法是什么?

Ree*_*sey 6

通过使用相同的计时,您可以获得与原始"中止"版本相同的结果.例如,这段代码:

static void Main()
{
    var clt = new CancellationTokenSource();
    clt.CancelAfter(1000);
    for (int i = 0; i < 10; i++)
    {
        Task.Run(() => Do(clt.Token));
    }
    Console.ReadLine();
}

private static void Do(CancellationToken cltToken)
{
    Console.WriteLine("Start " + Task.CurrentId);
    Thread.Sleep(2000);

    if (!cltToken.IsCancellationRequested)
    {
        Console.WriteLine("End " + Task.CurrentId);
    }
    else
    {
        Console.WriteLine("Cancelled "+ Task.CurrentId);
    }
}
Run Code Online (Sandbox Code Playgroud)

会产生类似于:

Start 111
Start 112
Start 113
Start 114
Start 115
Start 116
Start 117
Start 118
Start 119
Start 120
Cancelled 111
Cancelled 112
Cancelled 118
Cancelled 116
Cancelled 114
Cancelled 113
Cancelled 117
Cancelled 115
Cancelled 119
Cancelled 120
Run Code Online (Sandbox Code Playgroud)

使用a CancellationTokenSource比中止线程更好. Thread.Abort是一个坏主意,因为它在没有提供适当的清理机制的情况下中止线程.使用令牌可以让您以干净的方式协同处理取消.

至于为什么你的其他选项不能正常运作 - 你使用的时间有点太靠近了.在调试器下运行时尤其如此,因为它会阻止定时(即:CancelAfter以及同时Thread.Sleep)触发.如果在Visual Studio主机进程之外运行发布版本,您可能会发现它们的工作更加可靠.