'How do I appropriately dispose of a cancellation token from a continued task?
I created this DelayedTask class below that fires an action after a delay. I realize this functionality is built into Task, but I want to understand what is wrong with this code.
Every now and then I will get a null reference exception within the Begin() function on the _cancellationTokenSource. I'm not sure how that is possible. I tried lock in the CompleteTask function, but that didn't fix it.
I must be cancelling in close proximity to starting a new DelayedTask, but I'm not sure how to write this for it to work properly.
Feel free to critique anything else in this class. I appreciate the feedback.
public class DelayedTask
{
public bool IsCompleted { get; private set; }
public bool IsCompletedSuccessfully { get; private set; }
public bool IsCancelled { get; private set; }
public bool IsFaulted { get; private set; }
public AggregateException Exception { get; private set; }
private readonly Action _action;
private readonly TimeSpan _delay;
private CancellationTokenSource _cancelTokenSource;
private readonly object _lock = new object();
private DelayedTask(Action action, TimeSpan delay)
{
_action = action ?? throw new ArgumentNullException(nameof(action));
_delay = delay;
}
public async Task Begin()
{
_cancelTokenSource = new CancellationTokenSource();
try
{
Task delayTask = Task.Delay(_delay, _cancelTokenSource.Token).ContinueWith(CompleteTask);
await delayTask.ConfigureAwait(false);
}
catch (TaskCanceledException)
{
IsCancelled = true;
IsCompleted = true;
return;
}
}
private void CompleteTask(Task t)
{
lock(_lock)
{
if (!t.IsCanceled)
{
try
{
_action.Invoke();
IsCompletedSuccessfully = true;
}
catch (Exception ex)
{
Exception = new AggregateException($"Action failed to complete successfully", ex);
IsFaulted = true;
}
}
else
IsCancelled = true;
IsCompleted = true;
t.Dispose();
_cancelTokenSource?.Dispose();
_cancelTokenSource = null;
}
}
public void Reset()
{
//await Task.Run(() => Cancel());
Cancel();
IsCompleted = false;
IsCancelled = false;
IsFaulted = false;
IsCompletedSuccessfully = false;
Exception = null;
_ = Task.Run(() => Begin());
}
public void Cancel()
{
if (_cancelTokenSource == null || _cancelTokenSource.IsCancellationRequested)
return;
_cancelTokenSource.Cancel();
}
public static DelayedTask Set(Action action, TimeSpan delay)
{
DelayedTask result = new(action, delay);
_ = result.Begin();
return result;
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
