正确的方法来延迟任务的开始

时间:2011-02-14 09:33:57

标签: c# .net task-parallel-library

我想安排一个任务以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);

但我觉得应该有一个更好的方法,因为这会在睡眠中耗尽一个线程,在此期间它可以被取消。

我的其他选择是什么?

6 个答案:

答案 0 :(得分:28)

Damien_The_Unbeliever mentioned类似,异步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;
    }
}

答案 1 :(得分:19)

由于.NET 4.5现已发布,因此有一种非常简单的内置方式来延迟任务:只需使用Task.Delay()。在幕后,它使用ohadsc decompiled

的实现

答案 2 :(得分:8)

将来的正确答案可能是Task.Delay。但是,目前只能通过Async CTP(在CTP中,它在TaskEx而不是Task)上提供。

不幸的是,因为它仅在CTP中,所以没有很多很好的文档链接。

答案 3 :(得分:4)

查看"Parallel Programming with .NET 4 Samples"中的TaskFactoryExtensions_Delayed。

答案 4 :(得分:4)

我还没有对此进行过测试,但这是第一次使用包装器方法创建初始“延迟”任务或在延迟后继续。如果您发现问题,请随时纠正。

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

答案 5 :(得分:3)

您可以使用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();
}