C# 何时处置CancellationTokenSource?

C# 何时处置CancellationTokenSource?,c#,c#-4.0,task-parallel-library,plinq,parallel-extensions,C#,C# 4.0,Task Parallel Library,Plinq,Parallel Extensions,类CancellationTokenSource是一次性的。快速查看Reflector可以证明KernelEvent是一种(很可能)非托管资源。 因为CancellationTokenSource没有终结器,如果我们不处理它,GC就不会处理它 另一方面,如果您查看MSDN文章中列出的示例,则只有一个代码片段处理令牌 在代码中处理它的正确方法是什么 如果不等待,使用启动并行任务,则无法包装代码。只有在你不等待的情况下取消才有意义 当然,您可以使用Dispose调用在任务上添加ContinueWit

CancellationTokenSource
是一次性的。快速查看Reflector可以证明
KernelEvent
是一种(很可能)非托管资源。 因为
CancellationTokenSource
没有终结器,如果我们不处理它,GC就不会处理它

另一方面,如果您查看MSDN文章中列出的示例,则只有一个代码片段处理令牌

在代码中处理它的正确方法是什么

  • 如果不等待,
    使用
    启动并行任务,则无法包装代码。只有在你不等待的情况下取消才有意义
  • 当然,您可以使用
    Dispose
    调用在任务上添加
    ContinueWith
    ,但这就是方法吗
  • 那可取消的PLINQ查询呢,它不同步返回,但只在最后做一些事情?比如说
    .ForAll(x=>Console.Write(x))
  • 它是可重复使用的吗?同一令牌是否可以用于多个调用,然后与主机组件(比如UI控件)一起处置
    因为它没有类似于
    Reset
    的方法来清理
    IsCancelRequested
    Token
    字段,所以我认为它不可重用,因此每次启动任务(或PLINQ查询)时,都应该创建一个新的任务。这是真的吗?如果是,我的问题是在那些
    CancellationTokenSource
    实例上处理
    Dispose
    的正确和推荐策略是什么?

    我在ILSpy中查看了
    CancellationTokenSource
    ,但我只能找到
    m_KerneleEvent
    ,它实际上是一个
    ManualResetEvent
    ,它是
    WaitHandle
    对象的包装类。这应该由GC正确处理。

    谈到是否真的有必要调用Dispose on
    CancellationTokenSource
    。。。我的项目内存泄漏,结果是
    CancellationTokenSource
    是问题所在

    我的项目有一个服务,它不断地读取数据库并启动不同的任务,我将链接的取消令牌传递给我的工作人员,因此即使他们完成了数据处理,也没有处理取消令牌,这导致了内存泄漏

    MSDN明确指出:

    请注意,使用完链接的令牌源后,必须对其调用
    Dispose
    。有关更完整的示例,请参阅


    我在实现中使用了
    ContinueWith

    您应该始终处理
    CancellationTokenSource

    如何处置它完全取决于场景。您提出了几种不同的方案

  • 使用
    仅在您正在等待的并行工作中使用
    CancellationTokenSource
    时有效。如果那是你的senario,那太好了,这是最简单的方法

  • 使用任务时,请按照指示使用
    ContinueWith
    任务来处置
    CancellationTokenSource

  • 对于plinq,您可以使用
    using
    ,因为您正在并行运行它,但等待所有并行运行的worker完成

  • 对于UI,您可以为每个未绑定到单个取消触发器的可取消操作创建一个新的
    CancellationTokenSource
    。维护一个
    列表
    并将每个源添加到列表中,在释放组件时处理所有源

  • 对于线程,创建一个连接所有工作线程的新线程,并在所有工作线程完成时关闭单个源。看


  • 总有办法的<代码>IDisposable始终应释放实例。示例通常不是这样,因为它们要么是显示核心使用情况的快速示例,要么是因为添加所演示类的所有方面对于示例来说过于复杂。样本只是一个样本,不一定(甚至通常)是生产质量代码。并不是所有的样本都可以复制到生产代码中。

    这个答案仍在谷歌搜索中出现,我相信投票的答案并没有给出全部内容。在查看了
    CancellationTokenSource
    (CTS)和
    CancellationToken
    (CT)之后,我认为对于大多数用例,以下代码序列是可以的:

    if (cancelTokenSource != null)
    {
        cancelTokenSource.Cancel();
        cancelTokenSource.Dispose();
        cancelTokenSource = null;
    }
    
    上面提到的
    m_kernelHandle
    内部字段是支持CTS和CT类中的
    WaitHandle
    属性的同步对象。仅当您访问该属性时,才会实例化该属性。因此,除非在
    任务中使用
    WaitHandle
    进行一些旧式线程同步,否则调用dispose将无效


    当然,如果您正在使用它,您应该按照上面其他答案的建议执行操作,并延迟调用
    Dispose
    ,直到使用该句柄的任何
    WaitHandle
    操作完成,因为如中所述,结果是未定义的。

    我认为当前的任何答案都不令人满意。经过研究,我发现Stephen Toub()的回答如下:

    视情况而定。 在.NET4中,CTS.Dispose有两个主要用途。如果 CancellationToken的WaitHandle已被访问(因此是惰性的) 分配它),Dispose将处理该句柄。此外,如果 CTS是通过CreateLinkedTokenSource方法Dispose创建的 将取消CTS与其链接到的令牌的链接。在.NET4.5中, Dispose还有一个用途,即如果CTS使用计时器 在封盖下(例如调用CancelAfter),计时器将 处置

    使用CancellationToken.WaitHandle非常罕见, 因此,通常情况下,使用Dispose后进行清理并不是一个很好的理由。 但是,如果您正在使用CreateLinkedTokenSource创建CTS,或者 如果您使用的是CTS的定时器功能,它可能会更有效 使用处置<
    public class CancelableExecution
    {
        private readonly bool _allowConcurrency;
        private Operation _activeOperation;
    
        // Represents a cancelable operation that signals its completion when disposed
        private class Operation : IDisposable
        {
            private readonly CancellationTokenSource _cts;
            private readonly TaskCompletionSource<bool> _completionSource;
            private bool _disposed;
    
            public Task Completion => _completionSource.Task; // Never fails
    
            public Operation(CancellationTokenSource cts)
            {
                _cts = cts;
                _completionSource = new TaskCompletionSource<bool>(
                    TaskCreationOptions.RunContinuationsAsynchronously);
            }
    
            public void Cancel() { lock (this) if (!_disposed) _cts.Cancel(); }
    
            void IDisposable.Dispose() // It is disposed once and only once
            {
                try { lock (this) { _cts.Dispose(); _disposed = true; } }
                finally { _completionSource.SetResult(true); }
            }
        }
    
        public CancelableExecution(bool allowConcurrency)
        {
            _allowConcurrency = allowConcurrency;
        }
        public CancelableExecution() : this(false) { }
    
        public bool IsRunning => Volatile.Read(ref _activeOperation) != null;
    
        public async Task<TResult> RunAsync<TResult>(
            Func<CancellationToken, Task<TResult>> action,
            CancellationToken extraToken = default)
        {
            if (action == null) throw new ArgumentNullException(nameof(action));
            var cts = CancellationTokenSource.CreateLinkedTokenSource(extraToken);
            using (var operation = new Operation(cts))
            {
                // Set this as the active operation
                var oldOperation = Interlocked.Exchange(ref _activeOperation, operation);
                try
                {
                    if (oldOperation != null && !_allowConcurrency)
                    {
                        oldOperation.Cancel();
                        await oldOperation.Completion; // Continue on captured context
                        // The Completion never fails
                    }
                    cts.Token.ThrowIfCancellationRequested();
                    var task = action(cts.Token); // Invoke on the initial context
                    return await task.ConfigureAwait(false);
                }
                finally
                {
                    // If this is still the active operation, set it back to null
                    Interlocked.CompareExchange(ref _activeOperation, null, operation);
                }
            }
            // The cts is disposed along with the operation
        }
    
        public Task RunAsync(Func<CancellationToken, Task> action,
            CancellationToken extraToken = default)
        {
            if (action == null) throw new ArgumentNullException(nameof(action));
            return RunAsync<object>(async ct =>
            {
                await action(ct).ConfigureAwait(false);
                return null;
            }, extraToken);
        }
    
        public Task CancelAsync()
        {
            var operation = Volatile.Read(ref _activeOperation);
            if (operation == null) return Task.CompletedTask;
            operation.Cancel();
            return operation.Completion;
        }
    
        public bool Cancel() => CancelAsync() != Task.CompletedTask;
    }
    
    private readonly CancelableExecution _cancelableExecution = new CancelableExecution();
    
    private async void btnExecute_Click(object sender, EventArgs e)
    {
        string result;
        try
        {
            Cursor = Cursors.WaitCursor;
            btnExecute.Enabled = false;
            btnCancel.Enabled = true;
            result = await _cancelableExecution.RunAsync(async ct =>
            {
                await Task.Delay(3000, ct); // Simulate some cancelable I/O operation
                return "Hello!";
            });
        }
        catch (OperationCanceledException)
        {
            return;
        }
        finally
        {
            btnExecute.Enabled = true;
            btnCancel.Enabled = false;
            Cursor = Cursors.Default;
        }
        this.Text += result;
    }
    
    private void btnCancel_Click(object sender, EventArgs e)
    {
        _cancelableExecution.Cancel();
    }