Task.Delay не отменили?

Я пытаюсь создать интерфейс для игры. Игра длится 1 минуту. Метод GetStop останавливается после 60-секундной игры. Метод play запускает игру, а метод quit завершает игру. Теперь в идеале я хочу, чтобы когда я выходил из игры через 30 секунд, таймер должен сбрасываться, а при нажатии кнопки «Воспроизвести» таймер снова должен работать в течение 1 минуты. Чтобы следующая игра шла 1 минуту. Если я снова нажму кнопку «Выход», таймер должен быть сброшен для следующей игры.

Однако, похоже, в моем коде есть определенная проблема. Всякий раз, когда я выполняю метод quit, таймер, кажется, сохраняется в этом состоянии. Итак, если я выйду из гонки через 30 секунд, следующая гонка продлится всего 30 секунд. Если я выйду из гонки через 50 секунд, следующая гонка продлится всего 10 секунд. В идеале таймер должен сбрасываться, но не сбрасывается.

У меня нет идей здесь. Может ли кто-нибудь дать несколько предложений ??

private async Task GetStop(CancellationToken token)
{ 
    await Task.Run(async () =>
    {
        token.ThrowIfCancellationRequested();
        await Task.Delay(TimeSpan.FromSeconds(60), token);

        token.ThrowIfCancellationRequested();
        if (!token.IsCancellationRequested)
        {
            sendMessage((byte)ACMessage.AC_ESCAPE); 
        }
    }, token);
}

public async void Play()
{         
        sendMessage((byte)ACMessage.AC_START_RACE); 
        _cts.Cancel();

        if (_cts != null)
        {
            _cts.Dispose();
            _cts = null;
        }
        _cts = new CancellationTokenSource(); 
        await GetStop(_cts.Token);
   }

public void Quit()
{
        _cts.Cancel();
        if (_cts != null)
        {
            _cts.Dispose();
            _cts = null;
        }
    //
}

person David Silwal    schedule 02.07.2018    source источник
comment
Я не знаю, правильно ли я понял ваш вопрос, но, возможно, вы думаете, что отмена Task останавливает выполнение процесса. Это неправда. Только сама задача в качестве оболочки для процесса перестанет ждать ее, а остальная часть вашего кода продолжит работу, включая процесс «внутри» задачи.   -  person Silvermind    schedule 02.07.2018
comment
Ваш код может остановиться только там, где вы ему позволите. Так, например, если вы поместите 5-минутный сон, этот сон продолжится. Что вам нужно, так это ваш игровой цикл, чтобы у него было время как часть его условий для обновления/обработки.. Вместо вызова gettop в начале установите переменная в настоящее время+‹временной интервал›, а затем проверить сейчас ‹ временной интервал   -  person BugFinder    schedule 02.07.2018
comment
Спасибо, не могли бы вы прислать мне пример или ссылку для подражания?   -  person David Silwal    schedule 02.07.2018
comment
Вы, вероятно, не будете довольны решением вашего вопроса здесь, если вы обрабатываете свой игровой цикл с помощью задач. Взгляните на это описание того, как обычно работает игровой цикл.   -  person nvoigt    schedule 13.08.2018


Ответы (2)


Я вижу, что ваш код может генерировать исключения в нескольких местах. Если вы перехватываете и игнорируете все исключения, возможно, вы не сможете увидеть причину, по которой время, токен отмены и задачи работают неправильно.

В первый момент я мог определить следующее:

private async Task GetStop(CancellationToken token)
{ 
    await Task.Run(async () =>
    {
        // I think you don't need to throw here
        token.ThrowIfCancellationRequested();

        // this will throw an Exception when cancelled
        await Task.Delay(TimeSpan.FromSeconds(60), token); 

        // again, I think you don't need to throw here
        token.ThrowIfCancellationRequested();

        if (!token.IsCancellationRequested)
        {
            sendMessage((byte)ACMessage.AC_ESCAPE); 
        }
    }, token);
}

public async void Play()
{         
        sendMessage((byte)ACMessage.AC_START_RACE); 

        // at some scenarios this may be null
        _cts.Cancel();

        if (_cts != null)
        {
            _cts.Dispose();
            _cts = null;
        }
        _cts = new CancellationTokenSource(); 
        await GetStop(_cts.Token);
   }

public void Quit()
{
        _cts.Cancel();
        if (_cts != null)
        {
            _cts.Dispose();
            _cts = null;
        }
}

Я создал консольное приложение, сделал несколько небольших модификаций, и здесь оно, кажется, работает просто отлично. Пожалуйста, взгляните:

public static class Program
{
    public static void Main(string[] args)
    {
        var game = new Game();

        game.Play();
        Task.Delay(5000).Wait();
        game.Quit();

        game.Play();
        Task.Delay(15000).Wait();
        game.Quit();

        game.Play();
        Task.Delay(65000).Wait();

        Console.WriteLine("Main thread finished");
        Console.ReadKey();

        // Output:
        //
        // Start race (-00:00:00.0050018)
        // Quit called (00:00:05.0163131)
        // Timeout (00:00:05.0564685)
        // Start race (00:00:05.0569656)
        // Quit called (00:00:20.0585092)
        // Timeout (00:00:20.1025051)
        // Start race (00:00:20.1030095)
        // Escape (00:01:20.1052507)
        // Main thread finished
    }
}

internal class Game
{
    private CancellationTokenSource _cts;

    // this is just to keep track of the behavior, should be removed
    private DateTime? _first;
    private DateTime First
    {
        get
        {
            if (!_first.HasValue) _first = DateTime.Now;
            return _first.Value;
        }
    }


    private async Task GetStop(CancellationToken token)
    {
        await Task.Run(async () =>
        {
            try
            {
                // we expect an exception here, if it is cancelled
                await Task.Delay(TimeSpan.FromSeconds(60), token);
            }
            catch (Exception)
            {
                Console.WriteLine("Timeout ({0})", DateTime.Now.Subtract(First));
            }

            if (!token.IsCancellationRequested)
            {
                Console.WriteLine("Escape ({0})", DateTime.Now.Subtract(First));
            }
        }, token);
    }

    public async void Play()
    {
        Console.WriteLine("Start race ({0})", DateTime.Now.Subtract(First));

        CancelAndDisposeCts();

        _cts = new CancellationTokenSource();
        await GetStop(_cts.Token);
    }

    public void Quit()
    {
        Console.WriteLine("Quit called ({0})", DateTime.Now.Subtract(First));
        CancelAndDisposeCts();
    }

    private void CancelAndDisposeCts()
    {
        // avoid copy/paste for the same behavior
        if (_cts == null) return;

        _cts.Cancel();
        _cts.Dispose();
        _cts = null;
    }
}

Я бы также посоветовал взглянуть на System.Threading.Timer., может быть, это может быть полезно для некоторых сценариев...

Удачи вам в игре!

person Anderson Rancan    schedule 06.08.2018
comment
@DavidSilwal рад, что это помогло вам. Не могли бы вы принять один из ответов? Спасибо - person Anderson Rancan; 19.06.2019

Для своих целей я создал оболочку под названием CancellableTask, которая может помочь вам достичь того, чего вы хотите. Вы можете создать задачу, передав delegate в качестве параметра конструктору, затем вы можете Run с задержкой или без. Это может быть Canceled в любое время, либо во время задержки, либо во время ее выполнения.

Вот класс:

public class CancellableTask
    {
        private CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
        private Task cancellationTask = null;
        private Action<Task> method;
        private int delayMilis;

        public bool Delayed { get; private set; }

        public TaskStatus TaskStatus => cancellationTask.Status;

        public CancellableTask(Action<Task> task)
        {
            method = task;
        }

        public bool Cancel()
        {
            if (cancellationTask != null && (cancellationTask.Status == TaskStatus.Running || cancellationTask.Status == TaskStatus.WaitingForActivation))
            {
                cancellationTokenSource.Cancel();
                cancellationTokenSource.Dispose();
                cancellationTokenSource = new CancellationTokenSource();
                return true;
            }
            return false;
        }

        public void Run()
        {
            Delayed = false;
            StartTask();
        }

        public void Run(int delayMiliseconds)
        {
            if(delayMiliseconds < 0)
                throw new ArgumentOutOfRangeException();

            Delayed = true;
            delayMilis = delayMiliseconds;
            StartDelayedTask();
        }

        private void DelayedTask(int delay)
        {
            CancellationToken cancellationToken = cancellationTokenSource.Token;
            try
            {
                cancellationTask =
                    Task.
                        Delay(TimeSpan.FromMilliseconds(delay), cancellationToken).
                        ContinueWith(method, cancellationToken);

                while (true)
                {
                    if (cancellationTask.IsCompleted)
                        break;

                    if (cancellationToken.IsCancellationRequested)
                    {
                        cancellationToken.ThrowIfCancellationRequested();
                        break;
                    }
                }
            }
            catch (Exception e)
            {
                //handle exception
                return;
            }

        }

        private void NormalTask()
        {
            CancellationToken cancellationToken = cancellationTokenSource.Token;
            try
            {
                cancellationTask =
                    Task.Run(() => method, cancellationToken);

                while (true)
                {
                    if (cancellationTask.IsCompleted)
                        break;

                    if (cancellationToken.IsCancellationRequested)
                    {
                        cancellationToken.ThrowIfCancellationRequested();
                        break;
                    }
                }
            }
            catch (Exception e)
            {
                //handle exception
                return;
            }
        }

        private void StartTask()
        {
            Task.Run(() => NormalTask());
        }

        private void StartDelayedTask()
        {
            Task.Run(() => DelayedTask(delayMilis));
        }

    }

И его можно использовать так:

var task = new CancellableTask(delegate
            {
               DoSomething(); // your function to execute
            });
task.Run(); // without delay
task.Run(5000); // with delay in miliseconds
task.Cancel(); // cancelling the task
person AndrejH    schedule 06.08.2018