5

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

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

得到结果:

在此处输入图像描述

但从安全的角度来看,我不确定它是否适合实际应用。我还在不同的变体中使用了 CancellationToken,但它并没有给我正确的结果,因为我在使用带有时间跨度的 CancellationToken 或 .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);
        }
    }

在此处输入图像描述

在这种情况下,必须取消所有任务,因为 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);
        }
    }

在此处输入图像描述

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

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

4

2 回答 2

7

You can get the same results as your original "abort" version by using the same timings. For example, this code:

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

Will produce something simliar to:

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

Using a CancellationTokenSource is a better option than aborting threads. Thread.Abort is a bad idea since it aborts the thread without providing proper cleanup mechanisms. Using the token allows you to cooperatively handle the cancellation in a clean manner.

As for why your other options were not functioning properly - The timings you used were a bit too close together. This is especially an issue when running under the debugger, as it will prevent the timings (ie: CancelAfter as well as Thread.Sleep) from firing at the same time. If you run a release build outside of the Visual Studio host process, you'll likely find that they work far more reliably.

于 2013-10-11T16:52:33.140 回答
1

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

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

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

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

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

于 2013-10-11T18:29:46.457 回答