类CancellationTokenSource是可丢弃的。在Reflector中快速查看可以证明使用了KernelEvent,这是一个(很可能)非托管资源。
由于CancellationTokenSource没有终结器,如果我们不释放它,GC就不会这样做。
另一方面,如果查看MSDN文章“托管线程中的取消”中列出的示例,就会发现只有一个代码片段处理了令牌。
在代码中处理它的正确方法是什么?
You cannot wrap code starting your parallel task with using if you do not wait for it. And it makes sense to have cancellation only if you do not wait.
Of course you can add ContinueWith on task with a Dispose call, but is that the way to go?
What about cancelable PLINQ queries, which do not synchronize back, but just do something at the end? Let's say .ForAll(x => Console.Write(x))?
Is it reusable? Can the same token be used for several calls and then dispose it together with the host component, let's say UI control?
因为它没有一个Reset方法来清理IsCancelRequested和Token字段,我认为它是不可重用的,因此每次你开始一个任务(或PLINQ查询),你应该创建一个新的。这是真的吗?如果是,我的问题是,在那些许多CancellationTokenSource实例上处理Dispose的正确和推荐策略是什么?
我写了一个线程安全的类,它将CancellationTokenSource绑定到Task,并保证CancellationTokenSource在其关联的Task完成时被释放。它使用锁来确保CancellationTokenSource在被释放期间或之后不会被取消。这样做是为了符合文档的规定:
只有在CancellationTokenSource对象上的所有其他操作完成后,才必须使用Dispose方法。
还有:
Dispose方法使CancellationTokenSource处于不可用状态。
这里是CancelableExecution类:
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 _completionSource;
private bool _disposed;
public Task Completion => _completionSource.Task; // Never fails
public Operation(CancellationTokenSource cts)
{
_cts = cts;
_completionSource = new TaskCompletionSource(
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(); }
}
}
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)
{
ArgumentNullException.ThrowIfNull(action);
CancellationTokenSource cts = CancellationTokenSource
.CreateLinkedTokenSource(extraToken);
using Operation operation = new(cts);
// Set this as the active operation
Operation oldOperation = Interlocked
.Exchange(ref _activeOperation, operation);
try
{
if (oldOperation is not null && !_allowConcurrency)
{
oldOperation.Cancel();
// The Operation.Completion never fails.
await oldOperation.Completion; // Continue on captured context.
}
cts.Token.ThrowIfCancellationRequested();
// Invoke the action on the initial SynchronizationContext.
Task<TResult> task = action(cts.Token);
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 operation is disposed here, along with the cts.
}
public Task RunAsync(Func<CancellationToken, Task> action,
CancellationToken extraToken = default)
{
ArgumentNullException.ThrowIfNull(action);
return RunAsync<object>(async ct =>
{
await action(ct).ConfigureAwait(false);
return null;
}, extraToken);
}
public Task CancelAsync()
{
Operation operation = Volatile.Read(ref _activeOperation);
if (operation is null) return Task.CompletedTask;
operation.Cancel();
return operation.Completion;
}
public bool Cancel() => CancelAsync().IsCompleted == false;
}
CancelableExecution类的主要方法是RunAsync和Cancel。默认情况下,并发(重叠)操作是不允许的,这意味着在开始新操作之前,第二次调用RunAsync将静默地取消并等待前一次操作(如果它仍在运行)的完成。
该类可用于任何类型的应用程序。它的主要用途是在UI应用程序中,在带有启动和取消异步操作按钮的表单中,或者在每次更改所选项时取消和重新启动操作的列表框中。下面是第一个用例的示例:
private readonly CancelableExecution _cancelableExecution = new();
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。在高级场景中,提供这个可选令牌可能很有用。
要获得与. net Framework兼容的版本,您可以查看这个答案的第三版。
您应该始终处置CancellationTokenSource。
如何处置它完全取决于场景。你提出了几个不同的场景。
using only works when you're using CancellationTokenSource on some parallel work that you're waiting. If that's your senario, then great, it's the easiest method.
When using tasks, use a ContinueWith task as you indicated to dispose of CancellationTokenSource.
For plinq you can use using since you're running it in parallel but waiting on all of the parallel running workers to finish.
For UI, you can create a new CancellationTokenSource for each cancellable operation that is not tied to a single cancel trigger. Maintain a List<IDisposable> and add each source to the list, disposing all of them when your component is disposed.
For threads, create a new thread that joins all the worker threads and closes the single source when all of the worker threads finished. See CancellationTokenSource, When to dispose?
总有办法的。IDisposable实例应该总是被释放。示例通常不会,因为它们要么是显示核心用法的快速示例,要么是因为添加所演示的类的所有方面对于示例来说过于复杂。示例只是一个示例,不一定(甚至通常)是产品质量代码。并不是所有示例都可以原样复制到生产代码中。
这个答案仍然出现在谷歌的搜索中,我相信投票选出的答案并没有给出完整的故事。在查看了CancellationTokenSource (CTS)和CancellationToken (CT)的源代码后,我相信对于大多数用例,以下代码序列是好的:
if (cancelTokenSource != null)
{
cancelTokenSource.Cancel();
cancelTokenSource.Dispose();
cancelTokenSource = null;
}
上面提到的m_kernelHandle内部字段是CTS和CT类中支持WaitHandle属性的同步对象。只有当您访问该属性时,它才被实例化。因此,除非你在任务中使用WaitHandle进行一些老式的线程同步,否则调用dispose将没有任何效果。
当然,如果你正在使用它,你应该按照上面其他答案的建议去做,并延迟调用Dispose,直到使用该句柄的任何WaitHandle操作完成,因为,正如在WaitHandle的Windows API文档中所描述的那样,结果是未定义的。