Подтвердить что ты не робот

Каков правильный способ объединения долгосрочных задач с шаблоном async/await?

У меня есть класс таймера "High-Precision", который мне нужно для начала, остановки и паузы/возобновления. Для этого я связываю несколько различных примеров, которые я нашел в Интернете, но я не уверен, что я использую Tasks с 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();
        }
    }
}

Кто-нибудь может взглянуть и дать мне несколько указаний относительно того, правильно ли я делаю это?

UPDATE

Я пробовал модифицировать свой код для комментариев Noseratio ниже, но я до сих пор не могу понять синтаксис. Каждая попытка передать метод ExecuteAsync() для TaskFactory.StartNew или Task.Run приводит к ошибке компиляции, например:

"Вызов неоднозначен между следующими методами или свойствами: TaskFactory.StartNew(действие, 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);
4b9b3361

Ответ 1

Вот несколько моментов:

  • 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) фактически запрашивает паузу, но не синхронизируется с ней. Он будет продолжать выполнение после Pause, хотя сторона-производитель, возможно, еще не достигла ожидающего состояния, то есть await _pauseSource.Token.WaitWhilePausedAsync(). Это может быть нормально для вашей логики приложения, но вы должны знать об этом. Подробнее здесь.

[UPDATE] Ниже приведен правильный синтаксис для использования Factory.StartNew. Примечание Task<Task> и task.Unwrap. Также обратите внимание на _task.Wait() в Stop, чтобы убедиться, что задача завершена, когда возвращается Stop (аналогично Thread.Join). Кроме того, TaskScheduler.Default используется для указания Factory.StartNew использования планировщика пула потоков. Это важно, если вы создаете свой объект HighPrecisionTimer из другой задачи, которая, в свою очередь, была создана в потоке с контекстом синхронизации по умолчанию, например. (далее здесь и здесь).

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