[英]How to implement cancellation of shared Task:s in C#
所有這些都是關於在C#中取消任務的復雜案例的設計/最佳實踐的問題。 如何實現共享任務的取消?
作為一個最小的例子,我們假設以下內容; 我們有一個長期運行,可合作取消的“工作”操作。 它接受取消令牌作為參數,如果已被取消則拋出。 它在某個應用程序狀態下運行並返回一個值。 其結果由兩個UI組件獨立完成。
雖然應用程序狀態不變,但應該緩存Work函數的值,如果一個計算正在進行,則新請求不應該開始第二次計算,而是開始等待結果。
任何一個UI組件都應該能夠取消它的任務,而不會影響其他UI組件任務。
你到目前為止和我在一起嗎?
上面的內容可以通過在TaskCompletionSources中引入一個包裝真實Work任務的Task緩存來完成,其任務:然后返回給UI組件。 如果UI組件取消了它的Task,它只會放棄TaskCompletionSource Task而不是底層任務。 這一切都很好。 UI組件創建CancellationSource,取消請求是正常的自頂向下設計,底部是協作TaskCompletionSource任務。
現在,到了真正的問題。 應用程序狀態更改時該怎么辦? 讓我們假設讓“工作”功能在狀態副本上運行是不可行的。
一種解決方案是監聽任務緩存中的狀態變化(或那里)。 如果緩存具有底層任務使用的CancellationToken,即運行Work函數的任務,則可以取消它。 然后,這可以觸發取消所有附加的TaskCompletionSources Task:s,因此兩個UI組件都將獲得Canceled任務。 這是一種自下而上的取消。
有沒有一種首選的方法呢? 是否有一種設計模式將其描述在哪里?
可以實現自下而上取消,但感覺有點奇怪。 UI任務是使用CancellationToken創建的,但由於另一個(內部)CancellationToken而被取消。 此外,由於令牌不相同,因此不能在UI中忽略OperationCancelledException - 這會(最終)導致在外部Task:s終結器中拋出異常。
聽起來你想要一組貪婪的任務操作 - 你有一個任務結果提供者,然后構造一個任務集來返回第一個完成的操作,例如:
// Task Provider - basically, construct your first call as appropriate, and then
// invoke this on state change
public void OnStateChanged()
{
if(_cts != null)
_cts.Cancel();
_cts = new CancellationTokenSource();
_task = Task.Factory.StartNew(() =>
{
// Do Computation, checking for cts.IsCancellationRequested, etc
return result;
});
}
// Consumer 1
var cts = new CancellationTokenSource();
var task = Task.Factory.StartNew(() =>
{
var waitForResultTask = Task.Factory.StartNew(() =>
{
// Internally, this is invoking the task and waiting for it's value
return MyApplicationState.GetComputedValue();
});
// Note this task cares about being cancelled, not the one above
var cancelWaitTask = Task.Factory.StartNew(() =>
{
while(!cts.IsCancellationRequested)
Thread.Sleep(25);
return someDummyValue;
});
Task.WaitAny(waitForResultTask, cancelWaitTask);
if(cancelWaitTask.IsComplete)
return "Blah"; // I cancelled waiting on the original task, even though it is still waiting for it's response
else
return waitForResultTask.Result;
});
現在,我還沒有對此進行全面測試,但它應該允許您通過取消令牌來“取消”等待任務(從而強制“等待”任務先完成並點擊WaitAny
),並允許您“取消“計算任務。
另一件事是想出一個干凈的方法讓“取消”任務等待沒有可怕的阻止。 我認為這是一個好的開始。
這是我的嘗試:
// the Task for the current application state
Task<Result> _task;
// a CancellationTokenSource for the current application state
CancellationTokenSource _cts;
// called when the application state changes
void OnStateChange()
{
// cancel the Task for the old application state
if (_cts != null)
{
_cts.Cancel();
}
// new CancellationTokenSource for the new application state
_cts = new CancellationTokenSource();
// start the Task for the new application state
_task = Task.Factory.StartNew<Result>(() => { ... }, _cts.Token);
}
// called by UI component
Task<Result> ComputeResultAsync(CancellationToken cancellationToken)
{
var task = _task;
if (cancellationToken.CanBeCanceled && !task.IsCompleted)
{
task = WrapTaskForCancellation(cancellationToken, task);
}
return task;
}
同
static Task<T> WrapTaskForCancellation<T>(
CancellationToken cancellationToken, Task<T> task)
{
var tcs = new TaskCompletionSource<T>();
if (cancellationToken.IsCancellationRequested)
{
tcs.TrySetCanceled();
}
else
{
cancellationToken.Register(() =>
{
tcs.TrySetCanceled();
});
task.ContinueWith(antecedent =>
{
if (antecedent.IsFaulted)
{
tcs.TrySetException(antecedent.Exception.GetBaseException());
}
else if (antecedent.IsCanceled)
{
tcs.TrySetCanceled();
}
else
{
tcs.TrySetResult(antecedent.Result);
}
}, TaskContinuationOptions.ExecuteSynchronously);
}
return tcs.Task;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.