ฉันสร้างคลาส thread-safe ที่ผูก a CancellationTokenSource
ถึง a Task
และรับประกันว่าCancellationTokenSource
จะถูกกำจัดเมื่อความเกี่ยวข้องTask
เสร็จสมบูรณ์ มันใช้ล็อคเพื่อให้แน่ใจว่าCancellationTokenSource
จะไม่ถูกยกเลิกในระหว่างหรือหลังจากมันถูกกำจัด สิ่งนี้เกิดขึ้นเพื่อให้สอดคล้องกับเอกสารที่ระบุว่า:
Dispose
วิธีการจะต้องใช้เฉพาะเมื่อมีการดำเนินการอื่น ๆ ทั้งหมดเกี่ยวกับCancellationTokenSource
วัตถุได้เสร็จสิ้น
และยัง :
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
การจัดหาโทเค็นตัวเลือกนี้อาจมีประโยชน์ในสถานการณ์ที่ก้าวหน้า