班级CancellationTokenSource
是一次性的。快速查看Reflector可以证明KernelEvent
的使用,这是一种(非常可能的)非托管资源。
由于CancellationTokenSource
没有终结器,如果我们不处理它,GC将不会这样做。
另一方面,如果您查看MSDN文章Cancellation in Managed Threads中列出的示例,则只有一个代码段会丢弃该令牌。
在代码中处理它的正确方法是什么?
using
包装启动并行任务的代码。只有在你不等的时候取消才有意义。ContinueWith
电话在任务中添加Dispose
,但这是要走的路吗?.ForAll(x => Console.Write(x))
?因为它没有Reset
方法来清理IsCancelRequested
和Token
字段,所以我认为它不可重复使用,因此每次启动任务时(或者PLINQ查询)你应该创建一个新的。这是真的吗?如果是,我的问题是在这些Dispose
个实例上处理CancellationTokenSource
的正确和建议策略是什么?
答案 0 :(得分:59)
说到是否真的有必要在CancellationTokenSource
上调用Dispose ...我的项目中存在内存泄漏,结果发现CancellationTokenSource
是问题所在。
我的项目有一项服务,即不断阅读数据库并触发不同的任务,我将链接的取消令牌传递给我的工作人员,所以即使他们完成数据处理后,也没有处理取消令牌,这导致了记忆泄漏。
MSDN Cancellation in Managed Threads明确指出:
请注意,完成后,您必须在链接的令牌源上调用
Dispose
。有关更完整的示例,请参阅How to: Listen for Multiple Cancellation Requests。
我在实施中使用了ContinueWith
。
答案 1 :(得分:32)
我认为目前的答案都不令人满意。经过研究,我发现Stephen Toub的回复(reference):
这取决于。 在.NET 4中,CTS.Dispose有两个主要用途。如果 已经访问了CancellationToken的WaitHandle(因此很懒散 分配它),Dispose将处理该句柄。另外,如果 CTS是通过CreateLinkedTokenSource方法Dispose创建的 将CTS与其链接的令牌取消链接。在.NET 4.5中, Dispose有一个额外的目的,即如果CTS使用Timer 在封面下(例如,在调用CancelAfter时),Timer将是 弃置。
使用CancellationToken.WaitHandle非常罕见, 所以清理后通常不是使用Dispose的一个重要原因。 但是,如果您使用CreateLinkedTokenSource创建CTS,或者 如果你正在使用CTS'定时器功能,它可以更有影响力 使用Dispose。
我认为大胆的部分是重要的部分。他使用"更有影响力的"这让它有点模糊。我将其解释为意味着在这些情况下调用Dispose
应该完成,否则不需要使用Dispose
。
答案 2 :(得分:25)
我在ILSpy中查看CancellationTokenSource
,但我只能找到m_KernelEvent,它实际上是ManualResetEvent
,它是WaitHandle对象的包装类。这应该由GC正确处理。
答案 3 :(得分:18)
您应该始终处置CancellationTokenSource
。
如何处理它完全取决于场景。您提出了几种不同的方案。
using
仅在您正在等待的某些并行工作中使用CancellationTokenSource
时才有效。如果那是你的场景,那么很棒,这是最简单的方法。
使用任务时,请按照您的指示使用ContinueWith
任务处理CancellationTokenSource
。
对于plinq,您可以使用using
,因为您并行运行它,但等待所有并行运行的工作人员完成。
对于UI,您可以为每个可取消操作创建一个新的CancellationTokenSource
,该操作不依赖于单个取消触发器。维护List<IDisposable>
并将每个源添加到列表中,在处理组件时处置所有源。
对于线程,创建一个新线程,该线程连接所有工作线程,并在所有工作线程完成时关闭单个源。请参阅CancellationTokenSource, When to dispose?
总有办法。应始终处置IDisposable
个实例。样本通常不会因为它们是快速样本以显示核心使用情况,或者因为添加所示类的所有方面对于样本而言过于复杂。样本只是一个样本,不一定(甚至通常)生产质量代码。并非所有样本都可以按原样复制到生产代码中。
答案 4 :(得分:14)
这个答案仍在谷歌搜索中出现,我相信投票的答案并不是完整的故事。在查看CancellationTokenSource
CancellationToken
(CTS)和if (cancelTokenSource != null)
{
cancelTokenSource.Cancel();
cancelTokenSource.Dispose();
cancelTokenSource = null;
}
(CT)后,我相信对于大多数用例,以下代码序列都可以:
m_kernelHandle
上面提到的WaitHandle
内部字段是支持CTS和CT类中WaitHandle
属性的同步对象。只有在您访问该属性时才会实例化它。因此,除非您在Task
调用dispose中使用Dispose
进行某些旧式线程同步,否则将无效。
当然,如果您 使用它,您应该执行上述其他答案所建议的内容,并延迟调用WaitHandle
,直到使用句柄的任何{{1}}操作完成为止,因为,如source code中所述,结果未定义。
答案 5 :(得分:3)
问了很长时间以来,我得到了很多有用的答案,但是我遇到了一个与此相关的有趣问题,以为我会在这里将其作为其他答案:
仅当您确定没有人会尝试获取CTS的# create and format disk
Get-Disk |
Where PartitionStyle -eq 'Raw' |
Select-Object -First 1 |
Initialize-Disk -PartitionStyle MBR -PassThru |
New-Partition -DriveLetter F -UseMaximumSize |
Format-Volume -FileSystem NTFS -NewFileSystemLabel "Containers" -Confirm:$false
# move docker to F:\docker
docker images -a -q | %{docker rmi $_ --force}
Stop-Service Docker
$service = (Get-Service Docker)
$service.WaitForStatus("Stopped","00:00:30")
@{"data-root"="F:\docker"} | ConvertTo-Json | Set-Content C:\programdata\docker\config\daemon.json
Get-Process docker* | % {Stop-Process -Id $_.Id -Force}
docker system info
Copy-Item C:\programdata\docker F:\docker -Recurse
Start-Service Docker
属性时,才应致电CancellationTokenSource.Dispose()
。否则,您不应该不调用它,因为这是一场比赛。例如,在这里:
https://github.com/aspnet/AspNetKatana/issues/108
在此问题的修复程序中,以前执行Token
的代码被编辑为仅执行cts.Cancel(); cts.Dispose();
,因为有人不幸地尝试获取取消令牌以观察其取消状态不幸的是,在调用之后,除了他们计划的cts.Cancel();
之外,还需要处理ObjectDisposedException
。
与该修复程序有关的另一项主要观察结果是Tratcher做出的:“仅对于不会被取消的令牌需要进行处置,因为取消会进行所有相同的清理。”
即只做OperationCanceledException
而不是处理就足够了!
答案 6 :(得分:2)
我制作了一个线程安全的类,该类将CancellationTokenSource
绑定到Task
,并保证CancellationTokenSource
与其关联的Task
完成时将被处置。它使用锁来确保CancellationTokenSource
在处置期间或之后不会被取消。发生这种情况是为了遵守documentation,其中指出:
Dispose
对象上的所有其他操作都必须完成后才能使用CancellationTokenSource
方法。
还有also:
Dispose
方法使CancellationTokenSource
处于不可用状态。
这是课程:
public class CancelableExecution
{
private readonly bool _allowConcurrency;
private Operation _activeOperation;
private class Operation : IDisposable
{
private readonly object _locker = new object();
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 (_locker) if (!_disposed) _cts.Cancel();
}
void IDisposable.Dispose() // Is called only once
{
try
{
lock (_locker) { _cts.Dispose(); _disposed = true; }
}
finally { _completionSource.SetResult(true); }
}
}
public CancelableExecution(bool allowConcurrency)
{
_allowConcurrency = allowConcurrency;
}
public CancelableExecution() : this(false) { }
public bool IsRunning =>
Interlocked.CompareExchange(ref _activeOperation, null, null) != null;
public async Task<TResult> RunAsync<TResult>(
Func<CancellationToken, Task<TResult>> taskFactory,
CancellationToken extraToken = default)
{
var cts = CancellationTokenSource.CreateLinkedTokenSource(extraToken, default);
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
}
var task = taskFactory(cts.Token); // Run in 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);
}
}
}
public Task RunAsync(Func<CancellationToken, Task> taskFactory,
CancellationToken extraToken = default)
{
return RunAsync<object>(async ct =>
{
await taskFactory(ct).ConfigureAwait(false);
return null;
}, extraToken);
}
public Task CancelAsync()
{
var operation = Interlocked.CompareExchange(ref _activeOperation, null, null);
if (operation == null) return Task.CompletedTask;
operation.Cancel();
return operation.Completion;
}
public bool Cancel() => CancelAsync() != Task.CompletedTask;
}
CancelableExecution
类的主要方法是RunAsync
和Cancel
。默认情况下,不允许进行并发操作,这意味着在开始新操作之前,第二次调用RunAsync
将无提示地取消并等待上一个操作的完成(如果它仍在运行)。
此类可在任何类型的应用程序中使用。它的主要用法是在UI应用程序中,在带有用于启动和取消异步操作的按钮的窗体内部,或者与在每次更改选定项时取消和重新启动操作的列表框一样。这是第一种情况的示例:
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();
}
RunAsync
方法接受一个额外的CancellationToken
作为参数,该参数链接到内部创建的CancellationTokenSource
。提供此可选令牌可能对提前使用场景很有帮助。