问题描述
我有一个高精度"计时器类,我需要它能够启动,停止和启动.暂停/继续.为此,我将在互联网上找到的两个不同的示例联系在一起,但是我不确定我是否正确地使用了带有asnyc的任务/等待.
这是我的相关代码:
//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 都会导致编译错误,如下所示:/p>
以下方法或属性之间的调用不明确: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
的端)实际上仅请求暂停,但不对其进行同步.即使生产方可能尚未达到等待状态,即await _pauseSource.Token.WaitWhilePausedAsync()
,它也会在Pause
之后继续执行.这对于您的应用程序逻辑可能是可以的,但是您应该意识到这一点.更多信息此处.
[UPDATE] 以下是使用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();
}
}
}
I have a "High-Precision" timer class that I need to be able to be start, stop & pause / resume. To do this, I'm tying together a couple of different examples I found on the internet, but I'm not sure if I'm using Tasks with asnyc / await correctly.
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. Every attempt to pass the ExecuteAsync() method to either TaskFactory.StartNew or Task.Run, results in a compilation error like the following:
"The call is ambiguous between the following methods or properties: TaskFactory.StartNew(Action, CancellationToken...) and TaskFactory.StartNew<Task>(Func<Task>, CancellationToken...)".
Finally, is there a way to specify the LongRunning TaskCreationOption without having to provide a 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
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). Yourasync void ExecuteAsync()
returns instantly (as soon as the code flow reachesawait _pauseSource
inside it). Essentially, your_task
is in the completed state after that, while the rest ofExecuteAsync
will be executed unobserved (because it'svoid
). 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 useTask.Run
orTask.Factory.StartNew
instead ofnew Task
to start it. Because you want your task's action method beasync
, you'd be dealing with nested tasks here, i.e.Task<Task>
, whichTask.Run
would automatically unwrap for you. 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 callsPause
) actually only requests a pause, but doesn't synchronize on it. It will continue executing afterPause
, even though the producer side may not have reached the awaiting state yet, i.e.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
. Note Task<Task>
and 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
). Also, TaskScheduler.Default
is used to instruct Factory.StartNew
to use the thread pool scheduler. 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, e.g. a UI thread (more info here and here).
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();
}
}
}
这篇关于将长时间运行的任务与异步/等待模式相结合的正确方法是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!