安全地停止长时间运行的任务

时间:2012-05-02 07:20:24

标签: c# timeout task-parallel-library

我的问题是如何停止长时间运行的任务(.net 4)?我已经实现了TPL并尝试使用CancellationTokenSource,但它似乎不适用于我的场景。我见过的所有例子都假设你在while循环中工作,这样你就可以检查任务是否已被取消,而我只需要一个需要很长时间的操作。我不能等待工作完成,因为我需要假设它可能永远不会完成。 这是我尝试过的代码:

        bool? result = null;

        var cs = new CancellationTokenSource();
        var ct = cs.Token;

        var doWorkTask = new Task(() =>
        {
            Console.WriteLine("start dowork task");

            result = Work.LongRunning();
         }, ct);

        doWorkTask.Start();

        Task.WaitAny(new Task[] { doWorkTask }, timetowait);

        if (doWorkTask.IsCompleted)
        {
        Console.WriteLine("dowork task completed");

            doWorkTask.Dispose();
        }
        else
        {
        Console.WriteLine("dowork task has timedout");

            cs.Cancel();

            throw new TimeoutException("Timeout hit.");
        }

代码可以工作但是如果“超时”发生并且正在完成的工作访问“非托管代码”即资源,则永远不会处理任务。那说IsCancelledRequested不能在Work.LongRunning()中使用,所以我不能ThrowIfCancellationRequested。

我对其他想法持开放态度,并且我尝试过BackgroundWorker,但这似乎也不合适。

新例子:

var service = new System.ServiceProcess.ServiceController(ServiceName, ServerName);

        var serviceTask = Task.Factory.StartNew(() =>
        {
            result = (service.Status == ServiceControllerStatus.Running
                 || service.Status == ServiceControllerStatus.StartPending);
        }, cs.Token);

        serviceTask.Wait(2000, cs.Token);

        if (!serviceTask.IsCompleted)
        {
            cs.Cancel();
        }

2 个答案:

答案 0 :(得分:4)

以下是上述选项1的示例(仅在没有信号取消的情况下杀死任务)

class Program
    {
        private static void Main(string[] args)
        {
            Test test = new Test();
            test.Run();

            Console.WriteLine("Type c to cancel");
            if (Console.ReadLine().StartsWith("c"))
            {
                Console.WriteLine("cancellation requested");
                test.CancellationTokenSource.Cancel();
            }

            Console.ReadLine();
        }
    }

    public class Test
    {
        private void DoSomething()
        {
            Console.WriteLine("DoSomething runs for 30 seconds ");
            Thread.Sleep(new TimeSpan(0, 0, 0, 30));
            Console.WriteLine("woke up now ");
        }

        public CancellationTokenSource CancellationTokenSource = new CancellationTokenSource();

        public void Run()
        {
                var generateReportsTask = Task.Factory.StartNew(() =>
                {
                    CancellationTokenSource.Token.ThrowIfCancellationRequested();
                    Task doSomething = new Task(DoSomething, CancellationTokenSource.Token);
                    doSomething.Start();

                    doSomething.Wait(CancellationTokenSource.Token);
                }, CancellationTokenSource.Token);

                generateReportsTask.ContinueWith(
                    (t) =>
                    {
                        if (t.Exception != null)
                            Console.WriteLine("Exceptions reported :\n " + t.Exception);

                        if (t.Status == TaskStatus.RanToCompletion)
                            Console.WriteLine("Completed report generation task");
                        if (t.Status == TaskStatus.Faulted)
                            Console.WriteLine("Completed reported generation with unhandeled exceptions");
                        if(t.Status == TaskStatus.Canceled)
                            Console.WriteLine("The Task Has been cancelled");
                    });

        }
    }

答案 1 :(得分:2)

任务并行库专为CPU密集型工作而设计。 CPU密集型工作一蹴而就。如果您的Work.LongRunning()是CPU密集型的,您应该能够在内部传递取消令牌并取消它。如果它不是CPU密集型,那么你可以简单地丢弃最终回调的结果,而不是因为它只是在等待而停止实际的工作。

顺便说一句,如果你有等待(数据库调用或其他东西),你可能在底部的某处有异步方法。您可以显示Begin / End模式并将其包装在Task中。这个问题解释了如何:TPL TaskFactory.FromAsync vs Tasks with blocking methods这样你就可以避免占用通用线程,因为IO等待是以操作系统处理的特殊方式完成的。