首页 > 解决方案 > Task.Delay 没有被取消?

问题描述

我正在尝试为游戏构建界面。比赛进行1分钟。GetStop方法在游戏 60 秒后停止play方法启动游戏,quit方法退出游戏。现在理想情况下,当我在 30 秒后退出游戏时,计时器应该被重置,并且在单击“播放”按钮时,计时器应该再次运行 1 分钟。这样下一场比赛就可以运行 1 分钟。如果我再次按下退出按钮,则应为下一场比赛重置计时器。

但是,我的代码中似乎存在某个问题。每当我执行退出方法时,计时器似乎都保存在该状态。所以,如果我在 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;
        }
    //
}

标签: c#task-parallel-librarycancellationcancellationtokensource

解决方案


我可以看到您的代码可能会在多个地方引发异常。如果您正在捕获并忽略所有异常,您可能无法看到时间、取消令牌和任务无法正常工作的原因。

首先,我可以确定以下内容:

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,也许对某些场景有用......

祝你游戏好运!


推荐阅读