我写了一个线程安全的类,将
CancellationTokenSource
绑定到一个
Task
上,并确保当其关联的
Task
完成时,
CancellationTokenSource
将被释放。它使用锁来确保在
CancellationTokenSource
被释放期间和之后不会被取消。这是为了符合
文档 的规定:
只有在对 CancellationTokenSource
对象的所有其他操作完成后才能使用 Dispose
方法。
而且还有
以下内容:
Dispose
方法会使 CancellationTokenSource
处于不可用状态。
这里是
CancelableExecution
类:
public class CancelableExecution
{
private readonly bool _allowConcurrency;
private Operation _activeOperation;
private class Operation : IDisposable
{
private readonly CancellationTokenSource _cts;
private readonly TaskCompletionSource _completionSource;
private bool _disposed;
public Task Completion => _completionSource.Task;
public Operation(CancellationTokenSource cts)
{
_cts = cts;
_completionSource = new TaskCompletionSource(
TaskCreationOptions.RunContinuationsAsynchronously);
}
public void Cancel() { lock (this) if (!_disposed) _cts.Cancel(); }
void IDisposable.Dispose()
{
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);
Operation oldOperation = Interlocked
.Exchange(ref _activeOperation, operation);
try
{
if (oldOperation is not null && !_allowConcurrency)
{
oldOperation.Cancel();
await oldOperation.Completion;
}
cts.Token.ThrowIfCancellationRequested();
Task<TResult> task = action(cts.Token);
return await task.ConfigureAwait(false);
}
finally
{
Interlocked.CompareExchange(ref _activeOperation, null, operation);
}
}
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);
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();
}
< p >
RunAsync
方法接受一个额外的
CancellationToken
作为参数,该参数与内部创建的
CancellationTokenSource
相关联。在高级场景中,提供此可选令牌可能很有用。
对于与 .NET Framework 兼容的版本,您可以查看此答案的 第三个修订版本。
重要提示:CancellationTokenSource类实现了IDisposable接口。当您使用取消标记源完成后,应确保调用CancellationTokenSource.Dispose方法以释放其持有的任何非托管资源。
- https://learn.microsoft.com/en-us/dotnet/standard/threading/cancellation-in-managed-threads?view=netframework-4.8 - Endrju