web-dev-qa-db-ja.com

長時間実行されるタスクを非同期/待機パターンと組み合わせる正しい方法は何ですか?

開始、停止、一時停止/再開できるようにする必要がある「高精度」タイマークラスがあります。これを行うために、私はインターネットで見つけたいくつかの異なる例を結び付けていますが、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.StartNewまたはTask.Runのいずれかに渡そうとするすべての試み、次のようなコンパイルエラーが発生します。

「次のメソッドまたはプロパティ間で呼び出しがあいまいです:TaskFactory.StartNew(Action、CancellationToken ...)とTaskFactory.StartNew <Task>(Func <Task>、CancellationToken ...)」。

最後に、TaskSchedulerを提供せずにLongRunning TaskCreationOptionを指定する方法はありますか?

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);
21
Joshua Barker

ここにいくつかのポイントがあります:

  • _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_に注意してください。また、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();
        }
    }
}
_
18
noseratio