Как отменить и перезапустить задачу С#

У меня длительный, длинный интервал, процесс опроса. Мне нужно иметь возможность принудительно обновить и перезапустить опрос.

Самое очевидное, что пришло в голову, это отменить задачу опроса и запустить новую, так как первоначальный цикл всегда обновляется.

Я пытаюсь найти лучший способ сделать это, использование 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();
}

Но я не вижу способа дождаться завершения задачи путем отмены.


РЕДАКТИРОВАТЬ: RE - Дублирующий вопрос.

Принятый ответ на предыдущий вопрос заключался в использовании исключений так же, как я пытался избежать, однако оказалось, что второй ответ был полезен, но я не осознавал этого, пока не прочитал объяснение, предоставленное Мэттью Уотсон. Я рад, что это было закрыто как дубликат, хотя я не могу понять, как это сделать на самом деле!


person David Kirkpatrick    schedule 02.03.2017    source источник


Ответы (2)


Здесь есть несколько вопросов, поэтому я собираюсь предложить другой подход.

Во-первых, я отмечаю, что вызовы 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;
        }
    }
}
person Matthew Watson    schedule 02.03.2017
comment
Очень полезно спасибо. Время в моей ситуации должно быть do-while, так как я хочу, чтобы код выполнялся один раз сразу после активации. - person David Kirkpatrick; 02.03.2017
comment
@DavidKirkpatrick Думаю, вы можете настроить его по своему усмотрению. :) - person Matthew Watson; 02.03.2017

Вы можете сделать 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;
            }
        }
    }
person Community    schedule 02.03.2017