美好的一天!我正在为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:是的。如果你想要真正观察一个真正的取消标记,它会变得相当复杂一点,但如果它只是一个超时,那么它很简单。 :) –