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
,但这就是方法吗.ForAll(x=>Console.Write(x))
因为它没有类似于
Reset
的方法来清理IsCancelRequested
和Token
字段,所以我认为它不可重用,因此每次启动任务(或PLINQ查询)时,都应该创建一个新的任务。这是真的吗?如果是,我的问题是在那些CancellationTokenSource
实例上处理Dispose
的正确和推荐策略是什么?我在ILSpy中查看了CancellationTokenSource
,但我只能找到m_KerneleEvent
,它实际上是一个ManualResetEvent
,它是WaitHandle
对象的包装类。这应该由GC正确处理。谈到是否真的有必要调用Dispose onCancellationTokenSource
。。。我的项目内存泄漏,结果是CancellationTokenSource
是问题所在
我的项目有一个服务,它不断地读取数据库并启动不同的任务,我将链接的取消令牌传递给我的工作人员,因此即使他们完成了数据处理,也没有处理取消令牌,这导致了内存泄漏
MSDN明确指出:
请注意,使用完链接的令牌源后,必须对其调用Dispose
。有关更完整的示例,请参阅
我在实现中使用了
ContinueWith
。您应该始终处理CancellationTokenSource
如何处置它完全取决于场景。您提出了几种不同的方案
使用
仅在您正在等待的并行工作中使用CancellationTokenSource
时有效。如果那是你的senario,那太好了,这是最简单的方法ContinueWith
任务来处置CancellationTokenSource
using
,因为您正在并行运行它,但等待所有并行运行的worker完成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();
}