Tôi đã tạo một lớp an toàn luồng liên kết a CancellationTokenSource
với a Task
và đảm bảo rằng nó CancellationTokenSource
sẽ được xử lý khi liên kết của nó Task
hoàn thành. Nó sử dụng khóa để đảm bảo rằng CancellationTokenSource
sẽ không bị hủy trong hoặc sau khi nó đã được xử lý. Điều này xảy ra để tuân thủ các tài liệu , nói rằng:
Các Dispose
phương pháp duy nhất phải được sử dụng khi tất cả các hoạt động khác trên CancellationTokenSource
đối tượng đã hoàn thành.
Và cũng :
Các Dispose
phương pháp rời khỏi CancellationTokenSource
trong tình trạng không sử dụng được.
Đây là lớp học:
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;
}
Các phương thức chính của CancelableExecution
lớp là RunAsync
và Cancel
. Theo mặc định, các hoạt động đồng thời không được phép, có nghĩa là gọiRunAsync
lần thứ hai sẽ âm thầm hủy và chờ hoàn thành thao tác trước đó (nếu nó vẫn chạy), trước khi bắt đầu thao tác mới.
Lớp này có thể được sử dụng trong các ứng dụng dưới mọi hình thức. Mặc dù, cách sử dụng chính của nó là trong các ứng dụng UI, bên trong các biểu mẫu có các nút để bắt đầu và hủy hoạt động không đồng bộ hoặc với hộp danh sách hủy và khởi động lại hoạt động mỗi khi thay đổi mục được chọn. Dưới đây là một ví dụ về trường hợp đầu tiên:
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();
}
Các RunAsync
phương pháp chấp nhận thêm CancellationToken
như là đối số, đó là liên quan đến nội bộ tạo ra CancellationTokenSource
. Cung cấp mã thông báo tùy chọn này có thể hữu ích trong các tình huống tiến bộ.