如何取消并重新启动一个C#任务

3

我有一个长时间运行、长间隔轮询的进程。我需要能够强制更新并重新开始轮询。

最明显的想法是取消轮询任务,并启动一个新任务,因为初始循环总是会更新。

我正在尝试找出最好的方法来做到这一点,使用OperationCanceledException来控制程序流似乎很奇怪——但也许这是正确的选择?以下是我目前的代码:

 public void Start()
 {
     var previousDateTime = DateTime.MinValue;

     CancellationTokenSource = new CancellationTokenSource();
     CancellationToken = CancellationTokenSource.Token;

     ASMTask = Task.Run(async () =>
     {
         try
         {
             while (!CancellationToken.IsCancellationRequested)
             {
                 if (CheckForUpdate())
                 {
                     Update(previousDateTime);
                 }

                 await Task.Delay(PollingInterval * 1000, CancellationToken);
             }
             CancellationToken.ThrowIfCancellationRequested();
         }
         catch (OperationCanceledException oc)
         {
             Start();
         }
     }, CancellationToken);

 }

 public void ForceUpdate()
 {
     CancellationTokenSource.Cancel();
 }           

我不确定在任务内调用Start()方法会对资源产生什么影响?我猜测这是可以接受的,因为新任务将被分配一个线程来执行?

我想做这样的事情:

public void ForceUpdate()
{
    CancellationTokenSource.Cancel();
    ASMTask.WaitForCancellationComplete();
    Start();
}

但是我看不到等待任务完成的取消方式。


编辑:重复问题。

上一个问题的被接受答案使用了与我试图避免的相同的异常处理方法,然而第二个答案确实很有用,但在阅读Matthew Watson提供的解释之前我没有意识到这一点。 我很高兴将其作为重复问题关闭,尽管我无法弄清如何实际执行!

2个回答

4

您可以将Start方法设置为async(我添加了一行代码来模拟错误异常)。

    static int count;
    public async Task Start()
    {
        var previousDateTime = DateTime.MinValue;
        CancellationTokenSource = new CancellationTokenSource();
        CancellationToken = CancellationTokenSource.Token;
        try
        {
            while (!CancellationToken.IsCancellationRequested)
            {
                if (CheckForUpdate())
                {
                    Update(previousDateTime); // or better await UpdateAsync(previousDateTime);
                }

                await Task.Delay(PollingInterval * 1000, CancellationToken);
                Debug.WriteLine("here " + count);
                if (count>3)
                {
                    count = 0;
                    throw new Exception("simulate error");
                }
            }
            CancellationToken.ThrowIfCancellationRequested();
        }
        catch (OperationCanceledException oc)
        {
            Debug.WriteLine(oc.Message);
        }
    }

然后从事件中调用它,例如

    private async void button_Click(object sender, RoutedEventArgs e)
    {

        ASMTask = Start();
        await ASMTask;
    }

取消并重新启动任务,请使用 Task
    public async Task ForceUpdate()
    {
        CancellationTokenSource.Cancel();
        await ASMTask;
        count++;
        ASMTask = Start();
        await ASMTask;
    }

再次从事件处理程序中

    private async void button1_Click(object sender, RoutedEventArgs e)
    {
        if (ASMTask != null)
        {
            try
            {
                await ForceUpdate();
            }
            catch (Exception exc)
            {
                Debug.Write(exc.Message);
                ASMTask = Start();
                await ASMTask;
            }
        }
    }

3
这里存在一些问题,所以我将提出一种不同的方法。
首先,我注意到对 CheckForUpdate()Update() 的调用是同步的,因此使用 await 进行延迟可能没有太大用处——所以我将使用一种不同的方式进行延迟,同时仍允许延迟被中断。
我还将主方法分为两部分——一个外部控制循环和一个内部处理循环。
您没有提及控制外部循环寿命的任何方式,因此我将使用取消令牌来控制它。
我已经将这个方法组合成了一个可编译的控制台应用程序,演示了这种方法。请注意,因为这是一个控制台应用程序,我没有等待用于启动控制循环的任务。在实际代码中,您会在某个地方等待它。
using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication3
{
    class Program
    {
        static AutoResetEvent _restart = new AutoResetEvent(false);

        static void Main()
        {
            CancellationTokenSource cancellationSource = new CancellationTokenSource(10000); // Cancels after 10s.
            var task = Task.Run(() => ControlLoop(2, cancellationSource.Token, _restart));

            // After 5 seconds reset the process loop.

            Thread.Sleep(5000);
            Console.WriteLine("Restarting process loop.");
            RestartProcessLoop();

            // The cancellation source will cancel 10 seconds after it was constructed, so we can just wait now.
            Console.WriteLine("Waiting for control loop to terminate");
            task.Wait();

            Console.WriteLine("Control loop exited.");
        }

        public static void RestartProcessLoop()
        {
            _restart.Set();
        }

        public static async Task ControlLoop(int pollingIntervalSeconds, CancellationToken cancellation, AutoResetEvent restart)
        {
            while (!cancellation.IsCancellationRequested)
            {
                await Task.Run(() => ProcessLoop(pollingIntervalSeconds, cancellation, restart));
            }
        }

        public static void ProcessLoop(int pollingIntervalSeconds, CancellationToken cancellation, AutoResetEvent restart)
        {
            Console.WriteLine("Beginning ProcessLoop()");

            var previousDateTime = DateTime.MinValue;

            var terminators = new[]{cancellation.WaitHandle, restart};

            while (WaitHandle.WaitAny(terminators, TimeSpan.FromSeconds(pollingIntervalSeconds)) == WaitHandle.WaitTimeout)
            {
                if (CheckForUpdate())
                {
                    Update(previousDateTime);
                    previousDateTime = DateTime.Now;
                }
            }

            Console.WriteLine("Ending ProcessLoop()");
        }

        public static void Update(DateTime previousDateTime)
        {
        }

        public static bool CheckForUpdate()
        {
            Console.WriteLine("Checking for update.");
            return true;
        }
    }
}

非常有帮助,谢谢。在我的情况下,while需要改为do-while,因为我希望代码在激活时立即执行一次。 - David Kirkpatrick
@DavidKirkpatrick 我猜您可以按您的需求进行调整。 :) - Matthew Watson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接