将长时间运行的任务与异步/等待模式相结合的正确方法是什么? [英] What is correct way to combine long-running tasks with async / await pattern?

查看:42
本文介绍了将长时间运行的任务与异步/等待模式相结合的正确方法是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个高精度"计时器类,我需要它能够启动,停止和启动.暂停/继续.为此,我将在互联网上找到的两个不同的示例联系在一起,但是我不确定我是否正确地使用了带有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.RunTask.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线程(更多信息此处

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). Your async void ExecuteAsync() returns instantly (as soon as the code flow reaches await _pauseSource inside it). Essentially, your _task is in the completed state after that, while the rest of ExecuteAsync will be executed unobserved (because it's 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. Because you want your task's action method be async, you'd be dealing with nested tasks here, i.e. Task<Task>, which Task.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 calls Pause) actually only requests a pause, but doesn't synchronize on it. It will continue executing after Pause, 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();
        }
    }
}

这篇关于将长时间运行的任务与异步/等待模式相结合的正确方法是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆