按时间取消任务

时间:2013-10-11 16:40:49

标签: 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);
        }
    }

获得结果:

enter image description here

但从安全角度来看,我不确定它是否适合真正的应用。 我也在不同的变体中使用了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);
        }
    }

enter image description here

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

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

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

enter image description here

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

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

2 个答案:

答案 0 :(得分:5)

您可以使用相同的时间获得与原始“中止”版本相同的结果。例如,此代码:

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

会产生类似于:

的东西
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

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

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

答案 1 :(得分:1)

首先,您所看到的未发出取消令牌信号的问题可能是由于细微的时序变化。 CancelAfter应该可以正常工作,但是你需要增加超时和睡眠之间的差异,以便更准确地了解会发生什么。

其次,我可能是这里坏消息的预兆,但是如果这种非托管资源不能提供优雅地终止操作的机制,那么这只会指数级地变得更难。原因是:

  • 显然,当线程正在执行非托管代码时,无法轮询CancellationToken。因此,无法自行启动正常关机。
  • 除了你不应该中止一个线程这一事实之外,Thread.Abort调用不会将中止信号注入目标,直到它重新加入托管领域。换句话说,中止不会终止执行非托管代码的线程。这样做是为了让中止更安全。

使这种情况可靠地发生的唯一方法是在进程外运行非托管资源。这意味着您需要启动一个新进程来执行非托管代码,然后使用WCF(或其他通信协议)来回发送消息/数据。如果非托管资源未及时响应,则可以终止该进程。这是安全的,因为杀死另一个进程不会破坏当前进程的状态。

希望您使用的任何非托管资源都有一个内置优雅终止机制的API。如果写得好,它可能会有这样的功能,但我的经验表明很多都没有。

相关问题