美好的一天!我正在爲WinForms UI編寫一個幫助程序庫。使用TPL異步/的await機制啓動,得到了一個問題,這樣的代碼示例:如何處理TPL中的任務取消
private SynchronizationContext _context;
public void UpdateUI(Action action)
{
_context.Post(delegate { action(); }, null);
}
private async void button2_Click(object sender, EventArgs e)
{
var taskAwait = 4000;
var progressRefresh = 200;
var cancellationSource = new System.Threading.CancellationTokenSource();
await Task.Run(() => { UpdateUI(() => { button2.Text = "Processing..."; }); });
Action usefulWork =() =>
{
try
{
Thread.Sleep(taskAwait);
cancellationSource.Cancel();
}
catch { }
};
Action progressUpdate =() =>
{
int i = 0;
while (i < 10)
{
UpdateUI(() => { button2.Text = "Processing " + i.ToString(); });
Thread.Sleep(progressRefresh);
i++;
}
cancellationSource.Cancel();
};
var usefulWorkTask = new Task(usefulWork, cancellationSource.Token);
var progressUpdateTask = new Task(progressUpdate, cancellationSource.Token);
try
{
cancellationSource.Token.ThrowIfCancellationRequested();
Task tWork = Task.Factory.StartNew(usefulWork, cancellationSource.Token);
Task tProgress = Task.Factory.StartNew(progressUpdate, cancellationSource.Token);
await Task.Run(() =>
{
try
{
var res = Task.WaitAny(new[] { tWork, tProgress }, cancellationSource.Token);
}
catch { }
}).ConfigureAwait(false);
}
catch (Exception ex)
{
}
await Task.Run(() => { UpdateUI(() => { button2.Text = "button2"; }); });
}
基本上,這個想法是運行兩個並行任務 - 一個是,比如說,進度條或任何更新和排序的超時控制器,另一個是長時間運行的任務本身。無論哪個任務先完成取消另一個任務。所以,取消「進度」任務應該沒有問題,因爲它有一個循環,在該循環中我可以檢查任務是否被標記爲取消。問題在於長時間運行的問題。它可以是Thread.Sleep()或SqlConnection.Open()。當我運行CancellationSource.Cancel()時,長時間運行的任務繼續工作,不會取消。在超時之後,我對長時間運行的任務或任何可能導致的結果不感興趣。
由於混亂的代碼示例可能會提示,我嘗試了一堆變體,但都沒有給出我想要的效果。像Task.WaitAny()這樣的東西凍結UI ...有沒有辦法讓這種取消工作,或者甚至可能是一種不同的方法來編碼這些東西?
UPD:
public static class Taskhelpers
{
public static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs))
{
if (task != await Task.WhenAny(task, tcs.Task))
throw new OperationCanceledException(cancellationToken);
}
return await task;
}
public static async Task WithCancellation(this Task task, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs))
{
if (task != await Task.WhenAny(task, tcs.Task))
throw new OperationCanceledException(cancellationToken);
}
await task;
}
}
.....
var taskAwait = 4000;
var progressRefresh = 200;
var cancellationSource = new System.Threading.CancellationTokenSource();
var cancellationToken = cancellationSource.Token;
var usefulWorkTask = Task.Run(async() =>
{
try
{
System.Diagnostics.Trace.WriteLine("WORK : started");
await Task.Delay(taskAwait).WithCancellation(cancellationToken);
System.Diagnostics.Trace.WriteLine("WORK : finished");
}
catch (OperationCanceledException) { } // just drop out if got cancelled
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine("WORK : unexpected error : " + ex.Message);
}
}, cancellationToken);
var progressUpdatetask = Task.Run(async() =>
{
for (var i = 0; i < 25; i++)
{
if (!cancellationToken.IsCancellationRequested)
{
System.Diagnostics.Trace.WriteLine("==== : " + i.ToString());
await Task.Delay(progressRefresh);
}
}
},cancellationToken);
await Task.WhenAny(usefulWorkTask, progressUpdatetask);
cancellationSource.Cancel();
通過修改for (var i = 0; i < 25; i++)
限制i
我模仿長期運行的任務是否進度任務或以其他方式之前完成。按需要工作。 WithCancellation
輔助方法完成這項工作,儘管現在兩種'嵌套'Task.WhenAny
看起來很可疑。
'等待Task.WhenAny(tWork,tProgress,Task.Delay(5000));'非常簡單直接! –
@SergeMisnik:是的。如果你想要真正觀察一個真正的取消標記,它會變得相當複雜一點,但如果它只是一個超時,那麼它很簡單。 :) –