Наше приложение использует TPL для сериализации (потенциально) длительных единиц работы.Создание работы (заданий) осуществляется пользователем и может быть отменено в любое время.Чтобы иметь отзывчивый пользовательский интерфейс, если текущая часть работы больше не требуется, мы хотели бы отказаться от того, что мы делали, и немедленно начать другую задачу.
Задачи помещаются в очередь примерно так:
private Task workQueue;
private void DoWorkAsync
(Action<WorkCompletedEventArgs> callback, CancellationToken token)
{
if (workQueue == null)
{
workQueue = Task.Factory.StartWork
(() => DoWork(callback, token), token);
}
else
{
workQueue.ContinueWork(t => DoWork(callback, token), token);
}
}
Метод DoWork
содержит длительный вызов, так что это не так просто, как постоянно проверять состояние token.IsCancellationRequested
и выполнять сброс, если / когда обнаружена отмена.Длительная работа будет блокировать продолжения Задачи, пока она не завершится, даже если задача отменена.
Я предложил два примера методов, чтобы обойти эту проблему, но я не уверен, что оба из них являются правильными.Я создал простые консольные приложения, чтобы продемонстрировать, как они работают.
Важно отметить, что продолжение срабатывает до завершения исходной задачи .
Попытка № 1: внутренняя задача
static void Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource();
var token = cts.Token;
token.Register(() => Console.WriteLine("Token cancelled"));
// Initial work
var t = Task.Factory.StartNew(() =>
{
Console.WriteLine("Doing work");
// Wrap the long running work in a task, and then wait for it to complete
// or the token to be cancelled.
var innerT = Task.Factory.StartNew(() => Thread.Sleep(3000), token);
innerT.Wait(token);
token.ThrowIfCancellationRequested();
Console.WriteLine("Completed.");
}
, token);
// Second chunk of work which, in the real world, would be identical to the
// first chunk of work.
t.ContinueWith((lastTask) =>
{
Console.WriteLine("Continuation started");
});
// Give the user 3s to cancel the first batch of work
Console.ReadKey();
if (t.Status == TaskStatus.Running)
{
Console.WriteLine("Cancel requested");
cts.Cancel();
Console.ReadKey();
}
}
Это работает, но задание "innerT" кажется мне чрезвычайно хитрым.Он также имеет недостаток, заключающийся в том, что я вынужден выполнять рефакторинг всех частей моего кода, которые ставятся в очередь, таким образом, требуя обертывания всех длительных вызовов в новой задаче.
Попытка # 2: TaskCompletionSource возиться
static void Main(string[] args)
{ var tcs = new TaskCompletionSource<object>();
//Wire up the token's cancellation to trigger the TaskCompletionSource's cancellation
CancellationTokenSource cts = new CancellationTokenSource();
var token = cts.Token;
token.Register(() =>
{ Console.WriteLine("Token cancelled");
tcs.SetCanceled();
});
var innerT = Task.Factory.StartNew(() =>
{
Console.WriteLine("Doing work");
Thread.Sleep(3000);
Console.WriteLine("Completed.");
// When the work has complete, set the TaskCompletionSource so that the
// continuation will fire.
tcs.SetResult(null);
});
// Second chunk of work which, in the real world, would be identical to the
// first chunk of work.
// Note that we continue when the TaskCompletionSource's task finishes,
// not the above innerT task.
tcs.Task.ContinueWith((lastTask) =>
{
Console.WriteLine("Continuation started");
});
// Give the user 3s to cancel the first batch of work
Console.ReadKey();
if (innerT.Status == TaskStatus.Running)
{
Console.WriteLine("Cancel requested");
cts.Cancel();
Console.ReadKey();
}
}
Опять же это работает, но теперь у меня есть две проблемы:
a) Мне кажется, что я злоупотребляю TaskCompletionSource, никогда не используя его результат, и простоустановив значение null, когда я закончу свою работу.
b) Чтобы правильно подключить продолжения, мне нужно сохранить дескриптор уникального TaskCompletionSource предыдущей единицы работы, а не задачу, которая была создана для него.Это технически возможно, но опять же кажется неуклюжим и странным.
Куда пойти отсюда?
Повторюсь, мой вопрос: один из этих методов "правильный "способ решения этой проблемы, или есть более правильное / элегантное решение, которое позволит мне преждевременно прервать длительное выполнение задачи и немедленно начать продолжение?"Я предпочитаю решение с низким уровнем воздействия, но я бы хотел провести огромный рефакторинг, если это правильно.
С другой стороны, TPL - даже правильный инструмент для работы, или яЯ упустил лучший механизм организации очередей задач.Моя целевая платформа - .NET 4.0.