Добрый день! Я пишу вспомогательную библиотеку для пользовательского интерфейса WinForms. Начал использовать механизм TPL async / await и столкнулся с проблемой с таким примером кода:
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 () замораживает пользовательский интерфейс ... Есть ли способ заставить эту отмену работать или может быть даже другой подход к кодированию этих вещей?
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
пока выглядят подозрительно.