CancellationTokenSource
をにバインドし、関連付けが完了したときにが破棄されることTask
を保証するスレッドセーフクラスを作成しました。ロックを使用して、廃棄中または廃棄後にキャンセルされないようにします。これは、次のように記載されているドキュメントに準拠するために発生します。CancellationTokenSource
Task
CancellationTokenSource
このメソッドは、オブジェクトDispose
に対する他のすべての操作が完了した場合にのみ使用する必要があります。CancellationTokenSource
そしてまた:
このDispose
メソッドは、CancellationTokenSource
を使用できない状態のままにします。
クラスは次のとおりです。
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;
}
CancelableExecution
クラスRunAsync
の主なメソッドはとですCancel
。デフォルトでは、同時操作は許可されていません。つまりRunAsync
、2回目の呼び出しは、新しい操作を開始する前に、サイレントにキャンセルし、前の操作の完了を待ちます(まだ実行中の場合)。
このクラスは、あらゆる種類のアプリケーションで使用できます。ただし、その主な用途は、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