首页 文章

将长时间运行的任务与async / await模式结合起来的正确方法是什么?

提问于
浏览
16

我有一个“高精度”计时器类,我需要能够启动,停止和暂停/恢复 . 为此,我将在互联网上找到的几个不同的例子捆绑在一起,但我不确定我是否正在使用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();
        }
    }
}

任何人都可以看看,并提供一些关于我是否正确这样做的指示?

UPDATE

我已尝试根据下面的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);

}

UPDATE 2

我想我已经把它缩小了,但仍然不确定正确的语法 . 这是创建任务的正确方法,以便消费者/调用代码继续运行,任务启动并在新的异步线程上启动吗?

_task = Task.Run(async () => await ExecuteAsync, _cancelSource.Token);

//**OR**

_task = Task.Factory.StartNew(async () => await ExecuteAsync, _cancelSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);

1 回答

  • 15

    以下是一些观点:

    • async void 方法仅适用于异步事件处理程序(more info) . 您的 async void ExecuteAsync() 立即返回(一旦代码流到达其中的 await _pauseSource ) . 基本上,你的 _task 在此之后处于完成状态,而 ExecuteAsync 的其余部分将被执行而不被观察(因为它是 void ) . 它甚至可能根本不会继续执行,具体取决于主线程(以及进程)何时终止 .

    • 鉴于此,您应该将其设为 async Task ExecuteAsync() ,并使用 Task.RunTask.Factory.StartNew 而不是 new Task 来启动它 . 因为你希望你的任务的动作方法是 async ,你将在这里处理嵌套任务,即 Task<Task>Task.Run 会自动为你解开 . 更多信息可以在herehere找到 .

    • PauseTokenSource采用以下方法(按设计,AFAIU):代码的消费者方(调用 Pause 的方法)实际上只请求暂停,但不同步 . 它将在 Pause 之后继续执行,即使 生产环境 者方可能尚未达到等待状态,即 await _pauseSource.Token.WaitWhilePausedAsync() . 这可能适用于您的应用程序逻辑,但您应该了解它 . 更多信息here .

    [UPDATE] 以下是使用 Factory.StartNew 的正确语法 . 注意 Task<Task>task.Unwrap . 另请注意 Stop 中的 _task.Wait() ,它确保在 Stop 返回时(以类似于 Thread.Join 的方式)完成任务 . 此外, TaskScheduler.Default 用于指示 Factory.StartNew 使用线程池调度程序 . 如果您从另一个任务中创建 HighPrecisionTimer 对象,这很重要,而另一个任务又是在具有非默认同步上下文的线程上创建的,例如, UI线程(更多信息herehere) .

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

相关问题