11

I have a class which spawns various tasks which can run indefinitely. When this object is disposed, I want to stop those tasks from running.

Is this the correct approach:

public class MyClass : IDisposable { // Stuff public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) { queueCancellationTokenSource.Cancel(); feedCancellationTokenSource.Cancel(); } } } 

1 Answer 1

18

You're on the right track. However, I would suggest waiting for the task to terminate before returning from the Dispose method, in order to avoid race conditions where the task continues to operate after the object has been disposed. Also dispose the CancellationTokenSource.

Update: If you're on .NET Core 3.0 or later, you should make your class implement IAsyncDisposable and await your task from the DisposeAsyncCore method. I've updated the example below to reflect this.

using System; using System.Threading; using System.Threading.Tasks; public class MyClass : IDisposable, IAsyncDisposable { private readonly CancellationTokenSource feedCancellationTokenSource = new CancellationTokenSource(); private readonly Task feedTask; public MyClass() { feedTask = Task.Factory.StartNew(() => { while (!feedCancellationTokenSource.IsCancellationRequested) { // do finite work } }); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) { feedCancellationTokenSource.Cancel(); feedTask.Wait(); feedCancellationTokenSource.Dispose(); feedTask.Dispose(); } } public async ValueTask DisposeAsync() { await DisposeAsyncCore().ConfigureAwait(false); Dispose(false); GC.SuppressFinalize(this); } protected virtual async ValueTask DisposeAsyncCore() { feedCancellationTokenSource.Cancel(); await feedTask.ConfigureAwait(false); feedCancellationTokenSource.Dispose(); feedTask.Dispose(); } } // Sample usage: public static class Program { public static async Task Main() { await using (new MyClass()) { // do something else } Console.WriteLine("Done"); } } 
Sign up to request clarification or add additional context in comments.

10 Comments

Just because it's safe to do so, since you're guaranteed that the task would have terminated by then. But you don't really need to.
Regarding my comment above: It would seem that the GC will only call the Finalizer: stackoverflow.com/questions/45036/… However, this means that even if the GC eventually decides to call the Finalizer, that it doesn't stop the task. Would it make sense to add ~MyClass() { Dispose(false); } AND also null the references to feedCancellationTokenSource and feedTask outside the if(disposing) block? This would cause an exception in the task - but at least it would stop! (I realize its not recommended to stop the task in the finalizer)
@phil_rawlings: The GC will only call the finalizer. You really can't do much during the finalizer calls: Per MSDN: “DO NOT access any finalizable objects in the finalizer code path, because there is significant risk that they will have already been finalized. For example, a finalizable object A that has a reference to another finalizable object B cannot reliably use B in A’s finalizer, or vice versa. Finalizers are called in a random order (short of a weak ordering guarantee for critical finalization).”
9 years later: is this a good pattern? You're calling Wait in the dispose path, thus blocking cleanup. That might have a significant performance hit.
@JHBonarius: I've updated the example to use IAsyncDisposable (which has been introduced since then). Whether you want to await the Task at all or just fire-and-forget its disposal depends on the actual use-case.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.