因此,只要应用程序正在运行或请求取消,我的应用程序就需要几乎连续地执行一个操作(每次运行之间有 10 秒左右的暂停)。它需要完成的工作最多可能需要 30 秒。
是否最好使用 System.Timers.Timer 并使用 AutoReset 来确保它不会在前一个“滴答”完成之前执行操作。
或者我应该在 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.
}
或者只是在使用其 AutoReset 属性的同时使用一个简单的计时器,然后调用 .Stop() 来取消它?
最佳答案
我会使用 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;
}
我选择了 ActionBlock<TInput>
采取 DateTimeOffset
structure ;你必须传递一个类型参数,它也可能传递一些有用的状态(如果你愿意,你可以改变状态的性质)。
另外,请注意 ActionBlock<TInput>
默认情况下一次只处理 一个 项目,所以你保证只会处理一个操作(意思是,当它调用 reentrancy 时你不必处理 Post
extension method回到自己身上)。
我还通过了 CancellationToken
structure ActionBlock<TInput>
的构造函数和 Task.Delay
method称呼;如果过程被取消,取消将在第一个可能的机会发生。
从那里开始,很容易重构您的代码来存储 ITargetBlock<DateTimeoffset>
interface由 ActionBlock<TInput>
实现(这是表示作为消费者的 block 的更高级别的抽象,您希望能够通过调用 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
方法现在可以说是一个创建“服务”的工厂。您可以控制它何时开始和停止,而且它是完全独立的。您不必将计时器的状态控制与代码的其他方面交织在一起。您只需创建 block ,启动它,并在完成后停止它。
更高效地使用线程/任务/资源
TPL 数据流中 block 的默认调度程序对于 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;
}
当然,编织 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);
}
关于c# - 实现永无止境的任务的正确方法。 (计时器与任务),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13695499/