Task.Delay 没有被取消?
Task.Delay didn’t get canceled?
我正在尝试为游戏构建界面。游戏 运行 秒,持续 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;
}
//
}
我看到您的代码可能在多处抛出异常。
如果您捕获并忽略所有异常,您可能看不到时间、取消令牌和任务无法正常工作的原因。
第一时间,我可以确定以下内容:
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,也许对某些情况有用...
祝你游戏顺利!
为了我自己的目的,我创建了一个名为 CancellableTask
的包装器,它可以帮助您实现您想要的。您可以通过将 delegate
作为参数传递给构造函数来创建任务,然后您可以 Run
它有延迟或没有延迟。它可以随时 Canceled
,在延迟期间或 运行 期间。
这是class:
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
我正在尝试为游戏构建界面。游戏 运行 秒,持续 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;
}
//
}
我看到您的代码可能在多处抛出异常。 如果您捕获并忽略所有异常,您可能看不到时间、取消令牌和任务无法正常工作的原因。
第一时间,我可以确定以下内容:
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,也许对某些情况有用...
祝你游戏顺利!
为了我自己的目的,我创建了一个名为 CancellableTask
的包装器,它可以帮助您实现您想要的。您可以通过将 delegate
作为参数传递给构造函数来创建任务,然后您可以 Run
它有延迟或没有延迟。它可以随时 Canceled
,在延迟期间或 运行 期间。
这是class:
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