C# 延迟任务开始的正确方法

C# 延迟任务开始的正确方法,c#,.net,task-parallel-library,C#,.net,Task Parallel Library,我想安排一个任务在x毫秒内开始,并且能够在它开始之前(或者仅仅在任务开始时)取消它 第一次尝试可能是这样的 var _cancelationTokenSource = new CancellationTokenSource(); var token = _cancelationTokenSource.Token; Task.Factory.StartNew(() => { token.ThrowIfCancellationRequested();

我想安排一个任务在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);
但我觉得应该有一个更好的方法,因为这会在睡眠中耗尽一个线程,在此期间可以取消它


我的其他选择是什么?

未来正确的答案可能是
任务。延迟
。然而,目前只有通过(在CTP中,它在TaskEx而不是Task上)才可用


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

看看延迟的TaskFactoryExtensions\u。

我没有测试过这一点,但这里是包装器方法的第一步,可以创建初始“延迟”任务或在延迟后继续。如果您发现问题,请随时更正

    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(int延迟、CancellationToken令牌)
{
var source=new TaskCompletionSource();
计时器=空;
定时器=新定时器(s=>
{
source.TrySetResult(null);
timer.Dispose();
},null,delay,-1);
token.Register(()=>source.TrySetCanceled());
返回源任务;
}
公共静态任务ContinueAfterDelay
(这项任务,
int延迟,动作继续,
取消令牌(令牌)
{
var source=new TaskCompletionSource();
计时器=空;
var startTimer=新操作(t=>
{
定时器=新定时器(s=>
{
source.TrySetResult(null);
timer.Dispose();
},null,delay,-1);
});
task.ContinueWith
(斯塔蒂默,
代币
TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.Current);
token.Register(()=>source.TrySetCanceled());
返回source.Task.ContinueWith(continuation,token);
}
与之类似,异步CTP包括
任务。延迟
。幸运的是,我们有:

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;
    }
}
公共静态类TaskEx
{
静态只读任务_sPreCompletedTask=GetCompletedTask();
静态只读任务_sPreCanceledTask=GetPreCanceledTask();
公共静态任务延迟(int dueTimeMs、CancellationToken CancellationToken)
{
如果(dueTimeMs<-1)
抛出新ArgumentOutOfRangeException(“dueTimeMs”、“到期时间无效”);
if(cancellationToken.IsCancellationRequested)
返回已取消的任务;
如果(dueTimeMs==0)
返回已完成的任务;
var tcs=new TaskCompletionSource();
var ctr=新的CancellationTokenRegistration();
变量计时器=新计时器(委托(对象自身)
{
ctr.Dispose();
((计时器)self.Dispose();
tcs.TrySetResult(空);
});
如果(cancellationToken.canbecancelled)
ctr=cancellationToken.Register(委托
{
timer.Dispose();
tcs.trysetconceled();
});
计时器更改(dueTimeMs,-1);
返回tcs.Task;
}
私有静态任务GetPreCanceledTask()
{
var source=new TaskCompletionSource();
source.TrySetCanceled();
返回源任务;
}
私有静态任务GetCompletedTask()
{
var source=new TaskCompletionSource();
source.TrySetResult(null);
返回源任务;
}
}

自从.NET 4.5发布以来,有一种非常简单的内置方式来延迟任务:只需使用。在幕后,它使用的实现是。

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

您可以在一行中使用
StartDelayTask
实现
ContinueAfterDelay
task.ContinueWith(t=>StartDelayTask(delay,token)).Unwrap().ContinueWith(ContinueWith,token)
另请参阅我对Microsoft实现的回答(更完整一点)你最好使用一个。它的开销真的不多,而且读起来也很好(因此它是可维护的)。@Richard执行几百个任务并不少见。这段代码不能很好地处理它。计时器可能是一个更好的选择,但我认为它仍然不能给我一个干净的取消选项。有些计时器(如果不是全部的话)不能保证在计时器停止后不会触发勾号事件,所以要小心。
.Delay()
和其他基于TAP的方法现在可通过提供给异步CTP之外的.NET 4.0。忽略它只在VS11上工作的说法,它在VS2010上工作得非常好,因为它只是一个库。我检查了这个,但我的印象是,这只是延迟了结果,而不是任务的实际执行?我不理解这段代码的某些内容:计时器委托是否捕获了新创建的ctr,而不是与实际代表一个委托的ctr相关的ctr??!关于我的obove评论:似乎我只需要去正确地研究闭包。@Elliot ctr是一个捕获变量。实际变量已被捕获,因此捕获后对其所做的更改仍然有效。Jon skeet有一篇关于它们的文章:根据MSDN WaitHandle.WaitOne也会阻止线程。阻止当前线程,直到当前WaitHandle接收到信号,使用32位有符号整数指定时间间隔(毫秒)