-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCancellableTask.cs
More file actions
131 lines (109 loc) · 3.14 KB
/
CancellableTask.cs
File metadata and controls
131 lines (109 loc) · 3.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Open.Threading.Tasks;
/// <summary>
/// A Task sub-class that simplifies cancelling.
/// </summary>
public class CancellableTask : Task, ICancellable
{
CancellationTokenSource? _tokenSource;
public bool Cancel(bool onlyIfNotRunning)
{
if (IsCanceled || IsFaulted || IsCompleted)
return false;
if (onlyIfNotRunning && Status == TaskStatus.Running)
return false;
var ts = Interlocked.Exchange(ref _tokenSource, null); // Cancel can only be called once.
if (ts is null) return false;
using (ts)
{
if (ts.IsCancellationRequested) return false;
ts.Cancel();
}
return true;
}
public bool Cancel() => Cancel(false);
protected static void Blank() { }
protected override void Dispose(bool disposing)
{
Cancel();
base.Dispose(disposing);
}
protected CancellableTask(Action? action, CancellationToken token)
: base(action ?? Blank, token)
{
}
protected CancellableTask(Action? action)
: base(action ?? Blank)
{
}
protected CancellableTask(CancellationToken token)
: this(Blank, token)
{
}
protected CancellableTask()
: this(Blank)
{
}
// Only allow for static initilialization because this owns the TokenSource.
public static CancellableTask Init(Action? action = null)
{
var ts = new CancellationTokenSource();
var token = ts.Token;
return new CancellableTask(action, token)
{
_tokenSource = ts // Could potentially call cancel before run actually happens.
};
}
public void Start(TimeSpan delay, TaskScheduler? scheduler = null)
{
if (delay < TimeSpan.Zero)
{
RunSynchronously();
}
else if (delay == TimeSpan.Zero)
{
if (scheduler is null)
Start();
else
Start(scheduler);
}
else
{
var runState = 0;
ContinueWith(_ =>
{
// If this is arbitrarily run before the delay, then cancel the delay.
if (Interlocked.Increment(ref runState) < 2)
Cancel();
});
Delay(delay, _tokenSource!.Token)
.OnFullfilled(() =>
{
Interlocked.Increment(ref runState);
this.EnsureStarted(scheduler);
});
}
}
public void Start(int millisecondsDelay, TaskScheduler? scheduler = default) => Start(TimeSpan.FromMilliseconds(millisecondsDelay), scheduler);
public static CancellableTask StartNew(TimeSpan delay, Action? action = null, TaskScheduler? scheduler = default)
{
var task = new CancellableTask(action);
task.Start(delay, scheduler);
return task;
}
public static CancellableTask StartNew(int millisecondsDelay, Action? action = null) => StartNew(TimeSpan.FromMilliseconds(millisecondsDelay), action);
public static CancellableTask StartNew(Action action, TimeSpan? delay = null, TaskScheduler? scheduler = default) => StartNew(delay ?? TimeSpan.Zero, action, scheduler);
public static CancellableTask StartNew(Action<CancellationToken> action, TimeSpan? delay = null, TaskScheduler? scheduler = default)
{
var ts = new CancellationTokenSource();
var token = ts.Token;
var task = new CancellableTask(() => action(token), token)
{
_tokenSource = ts
};
task.Start(delay ?? TimeSpan.Zero, scheduler);
return task;
}
}