【问题标题】:Correct way to delay the start of a Task延迟任务开始的正确方法
【发布时间】:2011-06-26 19:27:58
【问题描述】:

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

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

我还有哪些其他选择?

【问题讨论】:

  • 您最好使用timer
  • 它的开销真的不大,而且读起来非常好(所以它是可维护的)。
  • @Richard 触发几百个任务并不少见。而且这段代码处理不好。
  • 计时器可能是更好的选择,但我认为它仍然没有给我一个干净的取消选项。
  • 有些定时器,如果不是全部,不保证在定时器停止后不触发Tick事件,所以要小心。

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


【解决方案1】:

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

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

【讨论】:

  • .Delay() 和其他基于 TAP 的方法现在可通过 Async Targeting Pack 在异步 CTP 之外的 .NET 4.0 中使用。忽略它只适用于 VS11 的说法,它在 VS2010 上运行得非常好,因为它只是一个库。
【解决方案2】:

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

【讨论】:

  • 我检查了这个,但我的印象是这只是延迟结果,而不是任务的实际执行?
【解决方案3】:

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

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

【讨论】:

  • 您可以在一行中使用StartDelayTask 实现ContinueAfterDelaytask.ContinueWith(t =&gt; StartDelayTask(delay, token)).Unwrap().ContinueWith(continuation, token) 另请参阅我对微软实现的回答(更完整)
【解决方案4】:

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

【讨论】:

  • 我不明白这段代码的一些内容:计时器委托不是捕获新的 ctr,而不是与实际代表委托的 ctr 相关的 ctr 吗??!跨度>
  • 关于我上面的评论:看来我只需要去好好研究一下闭包。
  • @Elliot ctr 是一个捕获的变量。实际变量被捕获,因此在捕获后对其所做的更改仍然有效。 Jon skeet 有一篇关于它们的文章:csharpindepth.com/Articles/Chapter5/Closures.aspx
【解决方案5】:

由于 .NET 4.5 现已发布,有一个非常简单的内置方法可以延迟任务:只需使用 Task.Delay()。在幕后,它使用了ohadsc decompiled 的实现。

【讨论】:

    【解决方案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();
    }
    

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-09-14
    • 1970-01-01
    • 1970-01-01
    • 2022-09-30
    • 2013-12-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多