What is correct way to combine long-running tasks with async / await pattern?

async-await, c#, long-running-processes, task-parallel-library, xamarin.ios

Solution

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

Problem

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

Original source

Related problems