[API Proposal]: Cancel on disposed CancellationTokenSource to enable cancel and forget pattern
- Dominant language
- C#
- Stars
- 18.3k
- Forks
- 5.6k
- PR merge metrics
- PR metrics pending
Description
### Background and motivation
Because `CancellationTokenSource` has to be disposed, it is surprisingly difficult to work with in certain scenarios.
Suppose you have desktop app, and want to delegate some extensive work to a background thread (to not to hang UI thread). But as the app keeps operating, a user can change something, making the whole work irrelevant. So you want to cancel the work and immediately create and start a new one.
You can do that by using Task and CancellationTokenSource. To not have to deal with two separate objects, you can encapsulate them in let say Work class. But because CTS needs to be disposed, you always have to wait somewhere for the task to complete (either by normal finish or by cancel or by exception) to dispose CTS. But where to wait for it? It would be so much simpler, if you could just cancel the work and forget.
You could dispose CTS in `finally` block of your "doing stuff" method - this approach allows you to always dispose CTS (except when the CTS was cancelled so early, that the task has never started, but this is relatively easy to fix in Work.Cancel method).
But there is a problem with this approach. CTS could be already disposed (in finally block), when a user is calling Cancel method. So we have a race condition here. It is virtually impossible to fix without a locking. But with lock, you have to be very careful to not leek the lock context, because it could introduce deadlocks.
So as you can see, simple problem is surprisingly difficult to deal with.
If you could just cancel on possibly already disposed `CancellationTokenSource` and not have to worry about `ObjectDisposedException` 😌. Live would be so much easier.
I even found the same api proposal in the past - https://github.com/dotnet/runtime/issues/29970, so I assume I'm not alone.
### API Proposal
```csharp
bool CancellationTokenSource.TryCancel();
```
If `CancellationTokenSource` is disposed already, it should just does nothing and return `false`.
OR
```csharp
CancellationTokenSource.Cancel();
```
Just not throw and does nothing when CTS is already disposed.
But I'm assuming it's already too late for that, even though if someone observes ObjectDisposedException it's most certainly because of a bug (race condition, that would be fixed by this change) and not on purpose. But it is still a breaking change.
### API Usage
```csharp
CancellationTokenSource cts = new();
cts.Dispose();
bool cancelled = cts.TryCancel();//Not throws, just does nothing and returns false
```
### Alternative Designs
```csharp
CancellationTokenSource.Cancel();
```
without throwing (just do nothing) when CancellationTokenSource is already disposed.
### Risks
Second approach is technically a breaking change.
Contributor guide
Assessment
This issue has not been assessed yet.