[英]What is correct way to combine long-running tasks with async / await pattern?
I have a "High-Precision" timer class that I need to be able to be start, stop & pause / resume.我有一个“高精度”计时器 class,我需要它能够启动、停止和暂停/恢复。 To do this, I'm tying together a couple of different examples I found on the inte.net, but I'm not sure if I'm using Tasks with asnyc / await correctly.
为此,我将我在 inte.net 上找到的几个不同的例子结合在一起,但我不确定我是否正确地使用 Tasks 和 asnyc / await。
Here is my relevant code:这是我的相关代码:
//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();
}
}
}
Can anyone please take a look and provide me some pointers on whether I'm doing this correctly?任何人都可以看一下并为我提供一些关于我是否正确执行此操作的指示吗?
UPDATE更新
I have tried modifying my code per Noseratio's comments below, but I still cannot figure out the syntax.我已尝试根据下面 Noseratio 的评论修改我的代码,但我仍然无法弄清楚语法。 Every attempt to pass the ExecuteAsync() method to either TaskFactory.StartNew or Task.Run , results in a compilation error like the following:
每次尝试将ExecuteAsync()方法传递给TaskFactory.StartNew或Task.Run时,都会导致编译错误,如下所示:
"The call is ambiguous between the following methods or properties: TaskFactory.StartNew(Action, CancellationToken...) and TaskFactory.StartNew<Task>(Func<Task>, CancellationToken...)". “以下方法或属性之间的调用不明确:TaskFactory.StartNew(Action, CancellationToken...) 和 TaskFactory.StartNew<Task>(Func<Task>, CancellationToken...)”。
Finally, is there a way to specify the LongRunning TaskCreationOption without having to provide a TaskScheduler?最后,有没有一种方法可以指定 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);
}
UPDATE 2更新 2
I think I've narrowed this down, but still not sure about the correct syntax.我想我已经缩小了范围,但仍然不确定正确的语法。 Would this be the right way to create the task so that the consumer / calling code continues on, with the task spinning-up and starting on a new asynchronous thread?
这是否是创建任务的正确方法,以便消费者/调用代码继续,任务旋转并在新的异步线程上启动?
_task = Task.Run(async () => await ExecuteAsync, _cancelSource.Token);
//**OR**
_task = Task.Factory.StartNew(async () => await ExecuteAsync, _cancelSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
Here are some points: 以下是一些观点:
async void
methods are only good for asynchronous event handlers ( more info ). async void
方法仅适用于异步事件处理程序( 更多信息 )。 Your async void ExecuteAsync()
returns instantly (as soon as the code flow reaches await _pauseSource
inside it). 您的
async void ExecuteAsync()
立即返回(一旦代码流到达await _pauseSource
)。 Essentially, your _task
is in the completed state after that, while the rest of ExecuteAsync
will be executed unobserved (because it's void
). 本质上,你的
_task
在此之后处于完成状态,而其余的ExecuteAsync
将被执行而不被观察(因为它是void
)。 It may even not continue executing at all, depending on when your main thread (and thus, the process) terminates. 它甚至可能根本不会继续执行,具体取决于主线程(以及进程)何时终止。
Given that, you should make it async Task ExecuteAsync()
, and use Task.Run
or Task.Factory.StartNew
instead of new Task
to start it. 鉴于此,您应该使其成为
async Task ExecuteAsync()
,并使用Task.Run
或Task.Factory.StartNew
而不是new Task
来启动它。 Because you want your task's action method be async
, you'd be dealing with nested tasks here, ie Task<Task>
, which Task.Run
would automatically unwrap for you. 因为你希望你的任务的动作方法是
async
,所以你将在这里处理嵌套任务,即Task<Task>
, Task.Run
将为你自动解包。 More info can be found here and here . 更多信息可以在这里和这里找到。
PauseTokenSource
takes the following approach (by design, AFAIU): the consumer side of the code (the one which calls Pause
) actually only requests a pause, but doesn't synchronize on it. PauseTokenSource
采用以下方法(按设计,AFAIU):代码的消费者方(调用Pause
)实际上只请求暂停,但不同步它。 It will continue executing after Pause
, even though the producer side may not have reached the awaiting state yet, ie await _pauseSource.Token.WaitWhilePausedAsync()
. 它将在
Pause
后继续执行,即使生产者方可能尚未达到等待状态,即await _pauseSource.Token.WaitWhilePausedAsync()
。 This may be ok for your app logic, but you should be aware of it. 这可能适用于您的应用程序逻辑,但您应该了解它。 More info here .
更多信息在这里 。
[UPDATE] Below is the correct syntax for using Factory.StartNew
. [更新]以下是使用
Factory.StartNew
的正确语法。 Note Task<Task>
and task.Unwrap
. 注意
Task<Task>
和task.Unwrap
。 Also note _task.Wait()
in Stop
, it's there to make sure the task has completed when Stop
returns (in a way similar to Thread.Join
). 还要注意
Stop
_task.Wait()
,它确保在Stop
返回时完成任务(以类似于Thread.Join
的方式)。 Also, TaskScheduler.Default
is used to instruct Factory.StartNew
to use the thread pool scheduler. 此外,
TaskScheduler.Default
用于指示Factory.StartNew
使用线程池调度程序。 This is important if your create your HighPrecisionTimer
object from inside another task, which in turn was created on a thread with non-default synchronization context, eg a UI thread (more info here and here ). 如果您从另一个任务中创建
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();
}
}
}
I'm adding code for running long running task (infinite with cancelation) with internal sub tasks:我正在添加用于运行带有内部子任务的长时间运行任务(无限取消)的代码:
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.