Правильный способ отсрочить запуск задачи
Я хочу запланировать запуск задачи в x ms и ее отменить до ее запуска (или только в начале задания).
Первая попытка будет похожа на
var _cancelationTokenSource = new CancellationTokenSource();
var token = _cancelationTokenSource.Token;
Task.Factory.StartNew(() =>
{
token.ThrowIfCancellationRequested();
Thread.Sleep(100);
token.ThrowIfCancellationRequested();
}).ContinueWith(t =>
{
token.ThrowIfCancellationRequested();
DoWork();
token.ThrowIfCancellationRequested();
}, token);
Но я чувствую, что должен быть лучший способ, так как это будет использовать поток во время сна, во время которого его можно было бы отменить.
Каковы мои другие варианты?
Ответы
Ответ 1
Как упоминалось Damien_The_Unbeliever, Async CTP включает Task.Delay
. К счастью, у нас есть Reflector:
public static class TaskEx
{
static readonly Task _sPreCompletedTask = GetCompletedTask();
static readonly Task _sPreCanceledTask = GetPreCanceledTask();
public static Task Delay(int dueTimeMs, CancellationToken cancellationToken)
{
if (dueTimeMs < -1)
throw new ArgumentOutOfRangeException("dueTimeMs", "Invalid due time");
if (cancellationToken.IsCancellationRequested)
return _sPreCanceledTask;
if (dueTimeMs == 0)
return _sPreCompletedTask;
var tcs = new TaskCompletionSource<object>();
var ctr = new CancellationTokenRegistration();
var timer = new Timer(delegate(object self)
{
ctr.Dispose();
((Timer)self).Dispose();
tcs.TrySetResult(null);
});
if (cancellationToken.CanBeCanceled)
ctr = cancellationToken.Register(delegate
{
timer.Dispose();
tcs.TrySetCanceled();
});
timer.Change(dueTimeMs, -1);
return tcs.Task;
}
private static Task GetPreCanceledTask()
{
var source = new TaskCompletionSource<object>();
source.TrySetCanceled();
return source.Task;
}
private static Task GetCompletedTask()
{
var source = new TaskCompletionSource<object>();
source.TrySetResult(null);
return source.Task;
}
}
Ответ 2
Так как .NET 4.5 теперь выпущен, существует очень простой встроенный способ задержки задачи: просто используйте Task.Delay()
, за кулисами, он использует реализацию, которая ohadsc декомпилировалась.
Ответ 3
Правильный ответ в будущем, вероятно, будет Task.Delay
. Тем не менее, это доступно только через Async CTP (и в CTP, это на TaskEx, а не на Task).
К сожалению, поскольку он только в CTP, ссылок на документацию для него также мало.
Ответ 4
Посмотрите на TaskFactoryExtensions_Delayed в "Параллельное программирование с помощью .NET 4 Samples" .
Ответ 5
Я не тестировал это, но вот первый проход в методах обертки для создания начальной задачи "Задержка" или продолжения после задержки. Если вы найдете проблемы, не стесняйтесь исправить.
public static Task StartDelayTask(int delay, CancellationToken token)
{
var source = new TaskCompletionSource<Object>();
Timer timer = null;
timer = new Timer(s =>
{
source.TrySetResult(null);
timer.Dispose();
}, null, delay, -1);
token.Register(() => source.TrySetCanceled());
return source.Task;
}
public static Task ContinueAfterDelay
(this Task task,
int delay, Action<Task> continuation,
CancellationToken token)
{
var source = new TaskCompletionSource<Object>();
Timer timer = null;
var startTimer = new Action<Task>(t =>
{
timer = new Timer(s =>
{
source.TrySetResult(null);
timer.Dispose();
},null,delay,-1);
});
task.ContinueWith
(startTimer,
token,
TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.Current);
token.Register(() => source.TrySetCanceled());
return source.Task.ContinueWith(continuation, token);
}
Ответ 6
Вы можете использовать метод перегрузки Token.WaitHandle.WaitOne(int32 миллисекунды), чтобы указать количество миллисекунд, ожидающих вашей задачи. Но ключевое различие между Thread.Sleep(xxx) и Token.WaitHandle.WaitOne(xxx), который позже блокирует поток до истечения указанного времени или токена отменена.
Вот пример
void Main()
{
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var task = Task.Factory.StartNew(() =>
{
// wait for 5 seconds or user hit Enter key cancel the task
token.WaitHandle.WaitOne(5000);
token.ThrowIfCancellationRequested();
Console.WriteLine("Task started its work");
});
Console.WriteLine("Press 'Enter' key to cancel your task");
Console.Read();
tokenSource.Cancel();
}