所以,我的应用程序需要长达几乎连续执行动作(10秒左右,每次运行之间的停顿)作为应用程序正在运行或请求取消。 它需要做的工作有责任采取长达30秒的可能性。
是更好地使用System.Timers.Timer,并使用自动复位,确保之前的“滴答”已完成之前,不执行动作。
或者我应该使用LongRunning模式一般任务与取消标记,并有定期无限while里面调用动作做的工作与电话之间有10秒的Thread.Sleep循环? 而对于异步/等待模式,我不知道,因为我没有在工作的任何返回值将是适当这里。
CancellationTokenSource wtoken;
Task task;
void StopWork()
{
wtoken.Cancel();
try
{
task.Wait();
} catch(AggregateException) { }
}
void StartWork()
{
wtoken = new CancellationTokenSource();
task = Task.Factory.StartNew(() =>
{
while (true)
{
wtoken.Token.ThrowIfCancellationRequested();
DoWork();
Thread.Sleep(10000);
}
}, wtoken, TaskCreationOptions.LongRunning);
}
void DoWork()
{
// Some work that takes up to 30 seconds but isn't returning anything.
}
或者只是使用一个简单的定时器,而使用其自动复位性能,并调用.Stop()来取消它?
我会使用TPL数据流为这个(因为你正在使用.NET 4.5,它使用Task
内部)。 您可以轻松创建一个ActionBlock<TInput>
它的行动,并等待合适的时间量,其职位项目本身它的处理后。
首先,创建一个工厂,将创建永无止境的任务:
ITargetBlock<DateTimeOffset> CreateNeverEndingTask(
Action<DateTimeOffset> action, CancellationToken cancellationToken)
{
// Validate parameters.
if (action == null) throw new ArgumentNullException("action");
// Declare the block variable, it needs to be captured.
ActionBlock<DateTimeOffset> block = null;
// Create the block, it will call itself, so
// you need to separate the declaration and
// the assignment.
// Async so you can wait easily when the
// delay comes.
block = new ActionBlock<DateTimeOffset>(async now => {
// Perform the action.
action(now);
// Wait.
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken).
// Doing this here because synchronization context more than
// likely *doesn't* need to be captured for the continuation
// here. As a matter of fact, that would be downright
// dangerous.
ConfigureAwait(false);
// Post the action back to the block.
block.Post(DateTimeOffset.Now);
}, new ExecutionDataflowBlockOptions {
CancellationToken = cancellationToken
});
// Return the block.
return block;
}
我所选择的ActionBlock<TInput>
采取DateTimeOffset
结构 ; 你必须通过一个类型参数,它也可能通过一些有用的状态(你可以改变国家的性质,如果你想)。
另外,还要注意的是, ActionBlock<TInput>
默认程序在同一时间只有一个项目,所以你保证只有一个动作将被处理(意思是,你不会有处理重入 ,当它调用Post
扩展方法回到本身)。
我还通过了CancellationToken
结构的两者的构造ActionBlock<TInput>
和所述Task.Delay
方法调用; 如果此过程被取消,取消将在第一个可能的机会。
从那里,它是你的代码存储的简单重构ITargetBlock<DateTimeoffset>
接口通过实施ActionBlock<TInput>
这表示是消费者块的更高层次的抽象,并希望能够通过一个触发消费打电话到Post
扩展方法):
CancellationTokenSource wtoken;
ActionBlock<DateTimeOffset> task;
您StartWork
方法:
void StartWork()
{
// Create the token source.
wtoken = new CancellationTokenSource();
// Set the task.
task = CreateNeverEndingTask(now => DoWork(), wtoken.Token);
// Start the task. Post the time.
task.Post(DateTimeOffset.Now);
}
然后你的StopWork
方法:
void StopWork()
{
// CancellationTokenSource implements IDisposable.
using (wtoken)
{
// Cancel. This will cancel the task.
wtoken.Cancel();
}
// Set everything to null, since the references
// are on the class level and keeping them around
// is holding onto invalid state.
wtoken = null;
task = null;
}
你为什么要在这里使用TPL数据流? 有几个原因:
关注点分离
该CreateNeverEndingTask
方法现在是一个工厂,创建你的“服务”可以这么说。 您可以控制,当它启动和停止,它是完全独立的。 你不必交织你的代码的其他方面定时器的状态控制。 您只需创建块,启动和停止它,当你完成。
更有效地利用线程/任务/资源
在TPL数据流中的块的缺省调度程序为相同的Task
,这是线程池。 通过使用ActionBlock<TInput>
处理您的行动,以及对呼叫Task.Delay
,你得到的时候,你实际上并不做任何事情,你正在使用的线程控制。 当然,这实际上导致一些开销,当你酿出了新的Task
将处理的延续,但应该是小,考虑你是不是在紧密循环(你等待调用之间的10秒)处理此。
如果DoWork
函数实际上可制成awaitable(即,在它返回一个Task
),然后就可以(可能)更通过调整上述工厂方法采取优化这个Func<DateTimeOffset, CancellationToken, Task>
而不是一个Action<DateTimeOffset>
像这样:
ITargetBlock<DateTimeOffset> CreateNeverEndingTask(
Func<DateTimeOffset, CancellationToken, Task> action,
CancellationToken cancellationToken)
{
// Validate parameters.
if (action == null) throw new ArgumentNullException("action");
// Declare the block variable, it needs to be captured.
ActionBlock<DateTimeOffset> block = null;
// Create the block, it will call itself, so
// you need to separate the declaration and
// the assignment.
// Async so you can wait easily when the
// delay comes.
block = new ActionBlock<DateTimeOffset>(async now => {
// Perform the action. Wait on the result.
await action(now, cancellationToken).
// Doing this here because synchronization context more than
// likely *doesn't* need to be captured for the continuation
// here. As a matter of fact, that would be downright
// dangerous.
ConfigureAwait(false);
// Wait.
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken).
// Same as above.
ConfigureAwait(false);
// Post the action back to the block.
block.Post(DateTimeOffset.Now);
}, new ExecutionDataflowBlockOptions {
CancellationToken = cancellationToken
});
// Return the block.
return block;
}
当然,这将是很好的做法,编织CancellationToken
通过你的方法(如果它接受一个),这是在这里完成。
这意味着,你将有一个DoWorkAsync
方法具有以下特征:
Task DoWorkAsync(CancellationToken cancellationToken);
你不得不改变(仅略,你也不会在这里渗出关注点分离)的StartWork
法核算传递给新的签名CreateNeverEndingTask
方法,就像这样:
void StartWork()
{
// Create the token source.
wtoken = new CancellationTokenSource();
// Set the task.
task = CreateNeverEndingTask((now, ct) => DoWorkAsync(ct), wtoken.Token);
// Start the task. Post the time.
task.Post(DateTimeOffset.Now, wtoken.Token);
}
我找到了新的基于任务的界面是做这样的事情很简单 - 比使用Timer类更容易。
有一些可以让你的小例子调整。 代替:
task = Task.Factory.StartNew(() =>
{
while (true)
{
wtoken.Token.ThrowIfCancellationRequested();
DoWork();
Thread.Sleep(10000);
}
}, wtoken, TaskCreationOptions.LongRunning);
你可以这样做:
task = Task.Run(async () => // <- marked async
{
while (true)
{
DoWork();
await Task.Delay(10000, wtoken.Token); // <- await with cancellation
}
}, wtoken.Token);
这样,如果里面的取消将在瞬间发生Task.Delay
,而不是等待Thread.Sleep
来完成。
此外,使用Task.Delay
超过Thread.Sleep
意味着你不占用一个线程无所事事的睡眠时间。
如果你能,你也可以做DoWork()
接受取消标记,并取消将更加敏感。
以下是我想出了:
- 继承
NeverEndingTask
和覆盖ExecutionCore
你想要做的工作方法。 - 更改
ExecutionLoopDelayMs
允许你调整,例如,如果你想使用一个退避算法环路之间的时间。 -
Start/Stop
提供同步接口来启动/停止任务。 -
LongRunning
意味着你会得到每专用线程NeverEndingTask
。 - 这个类并不在不同于环路分配内存
ActionBlock
以上基于溶液。 - 下面的代码是小品,不一定是生产代码:)
:
public abstract class NeverEndingTask
{
// Using a CTS allows NeverEndingTask to "cancel itself"
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
protected NeverEndingTask()
{
TheNeverEndingTask = new Task(
() =>
{
// Wait to see if we get cancelled...
while (!_cts.Token.WaitHandle.WaitOne(ExecutionLoopDelayMs))
{
// Otherwise execute our code...
ExecutionCore(_cts.Token);
}
// If we were cancelled, use the idiomatic way to terminate task
_cts.Token.ThrowIfCancellationRequested();
},
_cts.Token,
TaskCreationOptions.DenyChildAttach | TaskCreationOptions.LongRunning);
// Do not forget to observe faulted tasks - for NeverEndingTask faults are probably never desirable
TheNeverEndingTask.ContinueWith(x =>
{
Trace.TraceError(x.Exception.InnerException.Message);
// Log/Fire Events etc.
}, TaskContinuationOptions.OnlyOnFaulted);
}
protected readonly int ExecutionLoopDelayMs = 0;
protected Task TheNeverEndingTask;
public void Start()
{
// Should throw if you try to start twice...
TheNeverEndingTask.Start();
}
protected abstract void ExecutionCore(CancellationToken cancellationToken);
public void Stop()
{
// This code should be reentrant...
_cts.Cancel();
TheNeverEndingTask.Wait();
}
}
文章来源: Proper way to implement a never ending task. (Timers vs Task)