[英]What is correct way to combine long-running tasks with async / await pattern?
我有一个“高精度”计时器 class,我需要它能够启动、停止和暂停/恢复。 为此,我将我在 inte.net 上找到的几个不同的例子结合在一起,但我不确定我是否正确地使用 Tasks 和 asnyc / await。
这是我的相关代码:
//based on http://haukcode.wordpress.com/2013/01/29/high-precision-timer-in-netc/
public class HighPrecisionTimer : IDisposable
{
Task _task;
CancellationTokenSource _cancelSource;
//based on http://blogs.msdn.com/b/pfxteam/archive/2013/01/13/cooperatively-pausing-async-methods.aspx
PauseTokenSource _pauseSource;
Stopwatch _watch;
Stopwatch Watch { get { return _watch ?? (_watch = Stopwatch.StartNew()); } }
public bool IsPaused
{
get { return _pauseSource != null && _pauseSource.IsPaused; }
private set
{
if (value)
{
_pauseSource = new PauseTokenSource();
}
else
{
_pauseSource.IsPaused = false;
}
}
}
public bool IsRunning { get { return !IsPaused && _task != null && _task.Status == TaskStatus.Running; } }
public void Start()
{
if (IsPaused)
{
IsPaused = false;
}
else if (!IsRunning)
{
_cancelSource = new CancellationTokenSource();
_task = new Task(ExecuteAsync, _cancelSource.Token, TaskCreationOptions.LongRunning);
_task.Start();
}
}
public void Stop()
{
if (_cancelSource != null)
{
_cancelSource.Cancel();
}
}
public void Pause()
{
if (!IsPaused)
{
if (_watch != null)
{
_watch.Stop();
}
}
IsPaused = !IsPaused;
}
async void ExecuteAsync()
{
while (!_cancelSource.IsCancellationRequested)
{
if (_pauseSource != null && _pauseSource.IsPaused)
{
await _pauseSource.Token.WaitWhilePausedAsync();
}
// DO CUSTOM TIMER STUFF...
}
if (_watch != null)
{
_watch.Stop();
_watch = null;
}
_cancelSource = null;
_pauseSource = null;
}
public void Dispose()
{
if (IsRunning)
{
_cancelSource.Cancel();
}
}
}
任何人都可以看一下并为我提供一些关于我是否正确执行此操作的指示吗?
更新
我已尝试根据下面 Noseratio 的评论修改我的代码,但我仍然无法弄清楚语法。 每次尝试将ExecuteAsync()方法传递给TaskFactory.StartNew或Task.Run时,都会导致编译错误,如下所示:
“以下方法或属性之间的调用不明确:TaskFactory.StartNew(Action, CancellationToken...) 和 TaskFactory.StartNew<Task>(Func<Task>, CancellationToken...)”。
最后,有没有一种方法可以指定 LongRunning TaskCreationOption 而无需提供 TaskScheduler?
async **Task** ExecuteAsync()
{
while (!_cancelSource.IsCancellationRequested)
{
if (_pauseSource != null && _pauseSource.IsPaused)
{
await _pauseSource.Token.WaitWhilePausedAsync();
}
//...
}
}
public void Start()
{
//_task = Task.Factory.StartNew(ExecuteAsync, _cancelSource.Token, TaskCreationOptions.LongRunning, null);
//_task = Task.Factory.StartNew(ExecuteAsync, _cancelSource.Token);
//_task = Task.Run(ExecuteAsync, _cancelSource.Token);
}
更新 2
我想我已经缩小了范围,但仍然不确定正确的语法。 这是否是创建任务的正确方法,以便消费者/调用代码继续,任务旋转并在新的异步线程上启动?
_task = Task.Run(async () => await ExecuteAsync, _cancelSource.Token);
//**OR**
_task = Task.Factory.StartNew(async () => await ExecuteAsync, _cancelSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
以下是一些观点:
async void
方法仅适用于异步事件处理程序( 更多信息 )。 您的async void ExecuteAsync()
立即返回(一旦代码流到达await _pauseSource
)。 本质上,你的_task
在此之后处于完成状态,而其余的ExecuteAsync
将被执行而不被观察(因为它是void
)。 它甚至可能根本不会继续执行,具体取决于主线程(以及进程)何时终止。
鉴于此,您应该使其成为async Task ExecuteAsync()
,并使用Task.Run
或Task.Factory.StartNew
而不是new Task
来启动它。 因为你希望你的任务的动作方法是async
,所以你将在这里处理嵌套任务,即Task<Task>
, Task.Run
将为你自动解包。 更多信息可以在这里和这里找到。
PauseTokenSource
采用以下方法(按设计,AFAIU):代码的消费者方(调用Pause
)实际上只请求暂停,但不同步它。 它将在Pause
后继续执行,即使生产者方可能尚未达到等待状态,即await _pauseSource.Token.WaitWhilePausedAsync()
。 这可能适用于您的应用程序逻辑,但您应该了解它。 更多信息在这里 。
[更新]以下是使用Factory.StartNew
的正确语法。 注意Task<Task>
和task.Unwrap
。 还要注意Stop
_task.Wait()
,它确保在Stop
返回时完成任务(以类似于Thread.Join
的方式)。 此外, TaskScheduler.Default
用于指示Factory.StartNew
使用线程池调度程序。 如果您从另一个任务中创建HighPrecisionTimer
对象,这很重要,而该任务又是在具有非默认同步上下文的线程上创建的,例如UI线程( 此处和此处有更多信息)。
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication
{
public class HighPrecisionTimer
{
Task _task;
CancellationTokenSource _cancelSource;
public void Start()
{
_cancelSource = new CancellationTokenSource();
Task<Task> task = Task.Factory.StartNew(
function: ExecuteAsync,
cancellationToken: _cancelSource.Token,
creationOptions: TaskCreationOptions.LongRunning,
scheduler: TaskScheduler.Default);
_task = task.Unwrap();
}
public void Stop()
{
_cancelSource.Cancel(); // request the cancellation
_task.Wait(); // wait for the task to complete
}
async Task ExecuteAsync()
{
Console.WriteLine("Enter ExecuteAsync");
while (!_cancelSource.IsCancellationRequested)
{
await Task.Delay(42); // for testing
// DO CUSTOM TIMER STUFF...
}
Console.WriteLine("Exit ExecuteAsync");
}
}
class Program
{
public static void Main()
{
var highPrecisionTimer = new HighPrecisionTimer();
Console.WriteLine("Start timer");
highPrecisionTimer.Start();
Thread.Sleep(2000);
Console.WriteLine("Stop timer");
highPrecisionTimer.Stop();
Console.WriteLine("Press Enter to exit...");
Console.ReadLine();
}
}
}
我正在添加用于运行带有内部子任务的长时间运行任务(无限取消)的代码:
Task StartLoop(CancellationToken cancellationToken)
{
return Task.Factory.StartNew(async () => {
while (true)
{
if (cancellationToken.IsCancellationRequested)
break;
await _taskRunner.Handle(cancellationToken);
await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken);
}
},
cancellationToken,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.