Правильный способ отсрочки начала Задачи - PullRequest
53 голосов
/ 14 февраля 2011

Я хочу запланировать запуск задачи в x мс и иметь возможность отменить ее до ее запуска (или только в начале задачи).

Первая попытка будет похожа на

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);

Но я чувствую, что должен быть лучший способ, так как это использовало бы поток во время сна, во время которого его можно было бы отменить.

Какие у меня есть другие варианты?

Ответы [ 6 ]

28 голосов
/ 30 января 2012

Как и Damien_The_Unbeliever упомянул , Async CTP включает в себя Task.Delay. К счастью, у нас есть Отражатель:

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;
    }
}
19 голосов
/ 14 сентября 2012

С тех пор как вышла .NET 4.5, есть очень простой встроенный способ отложить задачу: просто используйте Task.Delay(). за кулисами используется реализация, декомпилированная ohadsc .

8 голосов
/ 14 февраля 2011

Правильный ответ в будущем, вероятно, будет Task.Delay. Однако в настоящее время он доступен только через Async CTP (а в CTP он находится на TaskEx, а не на Task).

К сожалению, поскольку это только в CTP, хороших ссылок на документацию тоже не так много.

4 голосов
/ 09 июня 2011

Я не проверял это, но вот первый подход к методам-оберткам для создания начальной задачи «Задержка» или продолжения после задержки.Если вы обнаружили проблемы, не стесняйтесь исправить.

    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);
    }
4 голосов
/ 09 июня 2011

Посмотрите на TaskFactoryExtensions_Delayed в «Параллельное программирование с .NET 4 Samples» .

3 голосов
/ 19 апреля 2013

Вы можете использовать метод перегрузки 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();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...