在Dispose方法中取消任务

11

我有一个类,可以生成各种可以无限运行的任务。当这个对象被处理时,我想停止这些任务的运行。

这是否正确的方法:

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个回答

18

你正在正确的方向上前进。然而,我建议在从Dispose方法返回之前等待任务终止,以避免对象被处理后任务仍在操作而导致竞态条件。还要处理CancellationTokenSource

更新:如果你使用的是.NET Core 3.0或更高版本,应该让你的类实现IAsyncDisposable并从DisposeAsyncCore方法中等待任务。我已经更新了下面的示例来反映这一点。

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");
    }
}

2
仅仅因为这样做是安全的,因为你可以保证任务在那时已经终止。但是,你并不真正需要这样做。 - Douglas
1
关于我上面的评论:似乎GC只会调用Finalizer:https://dev59.com/questions/d3VD5IYBdhLWcg3wO5AD 然而,这意味着即使GC最终决定调用Finalizer,它也不会停止任务。 是否有意义在~MyClass() { Dispose(false); }中添加并将feedCancellationTokenSource和feedTask的引用置空if(disposing)块之外?这将导致任务异常 - 但至少它会停止!(我意识到在finalizer中停止任务是不推荐的) - phil_rawlings
1
@phil_rawlings:GC只会调用终结器。你在终结器调用期间真的不能做太多事情。根据MSDN的说法:“不要在终结器代码路径中访问任何可终结对象,因为它们有很大的风险已经被终结了。例如,一个具有对另一个可终结对象B引用的可终结对象A不能可靠地在A的终结器中使用B,反之亦然。终结器的调用顺序是随机的(除了针对关键终结的弱顺序保证)。” - Douglas
2
九年后:这是一个好的模式吗?您正在处置路径中调用“ Wait”,从而阻止清理。这可能会对性能造成重大影响。 - JHBonarius
2
@JHBonarius:我已经更新了示例,使用了IAsyncDisposable(自那时以来已经引入)。您是否想要等待Task的处理或只是快速处理其处置取决于实际用例。 - Douglas
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接