Jos*_*osh 85 c# multithreading timer task-parallel-library .net-4.5
因此,只要应用程序正在运行或请求取消,我的应用程序几乎需要连续执行操作(每次运行之间暂停10秒左右).它需要做的工作可能需要30秒.
是否更好地使用System.Timers.Timer并使用AutoReset确保它在前一个"tick"完成之前不执行操作.
或者我应该在LongRunning模式下使用带有取消令牌的常规任务,并且在其内部有一个常规的无限while循环调用在调用之间使用10秒Thread.Sleep执行工作的操作?至于async/await模型,我不确定它在这里是否合适,因为我没有任何工作的返回值.
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.
}
Run Code Online (Sandbox Code Playgroud)
或者只是在使用AutoReset属性时使用简单的计时器,并调用.Stop()取消它?
cas*_*One 92
我会使用TPL Dataflow(因为你使用的是.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;
}
Run Code Online (Sandbox Code Playgroud)
我选择了ActionBlock<TInput>
采取DateTimeOffset
结构 ; 你必须传递一个类型参数,它也可以通过一些有用的状态(如果你愿意,你可以改变状态的性质).
另请注意,ActionBlock<TInput>
默认情况下,一次只处理一个项目,因此您可以保证只处理一个操作(这意味着,当它自己调用扩展方法时,您不必处理重入).Post
我还将CancellationToken
结构传递ActionBlock<TInput>
给了Task.Delay
方法调用的构造函数; 如果该过程被取消,取消将在第一个可能的机会进行.
从那里,您可以轻松地重构代码来存储实现的ITargetBlock<DateTimeoffset>
接口ActionBlock<TInput>
(这是代表消费者块的更高级抽象,并且您希望能够通过调用Post
扩展方法来触发消费):
CancellationTokenSource wtoken;
ActionBlock<DateTimeOffset> task;
Run Code Online (Sandbox Code Playgroud)
你的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);
}
Run Code Online (Sandbox Code Playgroud)
然后你的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;
}
Run Code Online (Sandbox Code Playgroud)
为什么要在这里使用TPL Dataflow?原因如下:
关注点分离
该CreateNeverEndingTask
方法现在是一个工厂,可以创建您的"服务".你控制它何时开始和停止,它完全是独立的.您不必将计时器的状态控制与代码的其他方面交织在一起.您只需创建块,启动它,并在完成后停止它.
更有效地使用线程/任务/资源
TPL数据流中块的默认调度程序与a相同Task
,即线程池.通过使用ActionBlock<TInput>
处理您的操作以及调用Task.Delay
,您可以控制当您实际上没有执行任何操作时正在使用的线程.当然,这会产生一些开销,当你产生新的Task
将处理延续时,但这应该很小,考虑到你没有在紧密的循环中处理它(你在调用之间等待十秒).
如果DoWork
函数实际上可以等待(即,它返回一个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;
}
Run Code Online (Sandbox Code Playgroud)
当然,将通过编织CancellationToken
到您的方法(如果它接受一个)是很好的做法,这是在这里完成的.
这意味着您将拥有一个DoWorkAsync
具有以下签名的方法:
Task DoWorkAsync(CancellationToken cancellationToken);
Run Code Online (Sandbox Code Playgroud)
您必须更改(仅略微,并且您不会在此处分离关注点)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);
}
Run Code Online (Sandbox Code Playgroud)
por*_*ges 71
我发现新的基于任务的界面对于这样的事情来说非常简单 - 比使用Timer类更容易.
您可以对示例进行一些小的调整.代替:
task = Task.Factory.StartNew(() =>
{
while (true)
{
wtoken.Token.ThrowIfCancellationRequested();
DoWork();
Thread.Sleep(10000);
}
}, wtoken, TaskCreationOptions.LongRunning);
Run Code Online (Sandbox Code Playgroud)
你可以这样做:
task = Task.Run(async () => // <- marked async
{
while (true)
{
DoWork();
await Task.Delay(10000, wtoken.Token); // <- await with cancellation
}
}, wtoken.Token);
Run Code Online (Sandbox Code Playgroud)
这样,如果在内部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();
}
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
58660 次 |
最近记录: |