C# 延迟任务开始的正确方法
我想安排一个任务在x毫秒内开始,并且能够在它开始之前(或者仅仅在任务开始时)取消它 第一次尝试可能是这样的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();
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位有符号整数指定时间间隔(毫秒)