将长时间运行的任务与异步/等待模式相结合的正确方法是什么



我有一个"高精度"计时器类,我需要它能够启动、停止&暂停/恢复。为了做到这一点,我将在互联网上找到的几个不同的例子结合在一起,但我不确定我是否正确地使用了带有asnyc/await的Tasks。

这是我的相关代码:

//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.StartNewTask.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.RunTask.Factory.StartNew而不是new Task来启动它。因为你希望你的任务的操作方法是async,所以你在这里处理嵌套任务,即Task<Task>Task.Run会自动为你打开它。更多信息可以在这里和这里找到。

  • PauseTokenSource采用以下方法(根据设计,AFAIU):代码的使用者端(调用Pause的使用者端)实际上只请求暂停,但不同步。它将在Pause之后继续执行,即使生产者端可能还没有达到等待状态,即await _pauseSource.Token.WaitWhilePausedAsync()。这对你的应用程序逻辑来说可能没问题,但你应该意识到这一点。点击此处了解更多信息。

[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();
        }
    }
}

我正在添加代码,用于运行具有内部子任务的长时间运行任务(无限取消):

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);
}

相关内容

  • 没有找到相关文章