C#/VB.net 非同期処理(Task,async,await)をマスターする

非同期処理(Task)のプログラムに慣れるには、作り込むことが必要ですね。

非同期処理のよく利用する項目を一覧にしてみたので、これを参考に都度必要な箇所を見てみると良いですよ。

目次

同期処理と非同期処理について

 同期処理は、特定の処理を完了するまで、他の処理を一時停止させるものです。例えば、ボタンを押して、ファイルの保存や印刷を行っている間、ユーザーが他の操作を行えないようなものが同期処理になります。

 一方、非同期処理は、特定の処理を実行しながらも、他の処理を行えるようにするものです。例えば、バックグラウンドでの処理を行っている途中に、ユーザーが他の画面操作を行えるようなものが非同期処理になります。

タスクを作成する(バックグラウンドで動作する処理を作成する)

非同期処理を.netで利用するには、Taskクラスを利用し、 async/awaitキーワードで制御します。

var 変数 = new Task(処理関数);
Dim 変数 As New Task(処理関数)

 Taskクラスのインスタンス生成時に引数のないメソッドを渡します。

以下は、ボタンを押した際に処理関数をバックグラウンドで処理するサンプルです。

        Task _task;
        private void button1_Click(object sender, EventArgs e)
        {
            //メソッドよりタスクを作成する
            _task = new Task(OnWork);
            _task.Start();
        }

        private void OnWork()
        {
            //30秒間ラベルの時間を更新する
            for (int i = 0; i < 30; i++)
            {
                this.Invoke(new Action(() =>
                {
                   label1.Text = DateTime.Now.ToString("HH:MM:ss");
                }));
                System.Threading.Thread.Sleep(1000);
            }

        }
Private _task As Task
Private Sub Button1_Click(sender As Object, e As EventArgs)
    ' メソッドからタスクを作成する
    _task = New Task(AddressOf OnWork)
    _task.Start()
End Sub

Private Sub OnWork()
    ' 30秒間ラベルの時間を更新する
    For i As Integer = 0 To 29
        Me.Invoke(New Action(Sub()
                                 Label1.Text = DateTime.Now.ToString("HH:MM:ss")
                             End Sub))
        System.Threading.Thread.Sleep(1000)
    Next
End Sub

戻り値を持つタスクを作成する

 バックグラウンドの処理を行った後に、元のメソッドに戻り値を返すには、通常通り「return」で値を返します。また、非同期処理の終了をawaitキーワードをしようして待機します。「Await」キーワードを使用する場合、メソッドに「Async」キーワードを付ける必要があります。「Async」キーワードを付けることで、非同期処理を行うことができるようになります。

以下の例では、10秒間経過した後に、最終時刻を「return」キーワードを使用して、元のメソッドに返して表示します。

private async void Button1_Click(object sender, EventArgs e)
{
    // ボタンがクリックされたときに実行される処理

   // バックグラウンドで10秒間経過するまで処理を実行するタスクを作成し、終了を待機する
    var ret = await Task.Factory.StartNew(() =>
    {
        // 10秒間繰り返す
        for (int i = 0; i < 10; i++)
        {
            // ラベルに秒数を表示する
            this.Invoke(new Action(() =>
            {
                label1.Text = i.ToString() + "秒経過";
            }));
            // 1秒間待機する
            System.Threading.Thread.Sleep(1000);
        }
        // 最終時刻を返す
        return DateTime.Now;
    });
    // ラベルに最終時刻を表示する
    label1.Text = "最終時刻:" + ret.ToString();
}
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
   // ボタンがクリックされたときに実行される処理

  // バックグラウンドで10秒間経過するまで処理を実行するタスクを作成し、終了を待機する
	Dim ret = Await Task.Factory.StartNew(Function()
             // 10秒間繰り返す
												For i As Integer = 0 To 9
              // ラベルに秒数を表示する
													Me.Invoke(New Action(Sub()
																			Label1.Text = i.ToString() + "秒経過"
																		End Sub))
              // 1秒間待機する
													System.Threading.Thread.Sleep(1000)
												Next
             // 最終時刻を返す
												Return DateTime.Now
											End Function)
 // ラベルに最終時刻を表示する
	Label1.Text = "最終時刻:" & ret.ToString()

End Sub
CHECK!

タスクを生成して同時に処理を開始するには、TaskクラスのFactoryプロパティにあるStartNewメソッドを利用することができます。

複数のタスクの完了を待ち合わせする(WaitAllメソッド)

 複数のタスクの終了を待つには、TaskクラスのWaitAllメソッドを使用することができます。

WaitAllメソッドは、指定されたタスクの配列を受け取り、すべてのタスクが完了するまで待機します。 WaitAllメソッドを使用すると、複数のタスクを同時に動作させた後に、すべてのタスクが終了するまで待つことができます。


        private void Task1()
        {
            //5秒間、経過時間をラベルに表示する
            for(int i = 0; i < 5; i++)
            {
                this.Invoke(new Action(() =>
                                {label1.Text = i.ToString() + "秒経過";}));
                System.Threading.Thread.Sleep(1000);            
            }
        }

        private void Task2()
        {
            //10秒間、経過時間をラベルに表示する
            for (int i = 0; i < 10; i++)
            {
                this.Invoke(new Action(() =>
                    {
                        label2.Text = i.ToString() + "秒経過";
                    }));
                System.Threading.Thread.Sleep(1000);
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Task.Factory.StartNew(() =>
            {
                this.Invoke(new Action(() => { label3.Text = "タスク開始"; }));

                var task1 = Task.Factory.StartNew(() => Task1());
                var task2 = Task.Factory.StartNew(() => Task2());
                //タスクの完了を待ち合わせする
                Task.WaitAll(task1, task2);
                this.Invoke(new Action(() => { label3.Text = "タスク終了"; }));
            });
        }
Private Sub _Task1()
    '5秒間、経過時間をラベルに表示する
    For i As Integer = 0 To 4
        Me.Invoke(New Action(Sub()
                                    Label1.Text = i.ToString() & "秒経過"
                                End Sub))
        System.Threading.Thread.Sleep(1000)
    Next
End Sub

Private Sub _Task2()
    '10秒間、経過時間をラベルに表示する
    For i As Integer = 0 To 9
        Me.Invoke(New Action(Sub()
                                    Label2.Text = i.ToString() & "秒経過"
                                End Sub))
        System.Threading.Thread.Sleep(1000)
    Next
End Sub

Private Sub button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Task.Factory.StartNew(Sub()
                                Me.Invoke(New Action(Sub()
                                                        Label3.Text = "タスク開始"
                                                    End Sub))

                                Dim task1 = Task.Factory.StartNew(AddressOf _Task1)
                                Dim task2 = Task.Factory.StartNew(AddressOf _Task2)
                                'タスクの完了を待ち合わせする
                                Task.WaitAll(task1, task2)
                                Me.Invoke(New Action(Sub()
                                                        Label3.Text = "タスク終了"
                                                    End Sub))
                            End Sub)
End Sub
CHECK

StartNewメソッドは、新しいタスクを生成し、指定されたActionデリゲートを処理として実行します。Actionデリゲートは、void型を返す無名関数を表します。

タスクの終了時に続けて処理する(ContinueWithメソッド)

 1つのタスクの直後に処理をつなげたい場合は、ContinueWithメソッドを利用します。

 以下の例では、算出処理と表示する処理を別途記述してつなげて処理しています。

        Task<int> onTask()
        {
            var task = new Task<int>(() =>
            {
                //合計値を毎秒計算する
                int sum = 0;
                for (int i = 0; i < 10; i++)
                {
                    this.Invoke(new Action(() =>
                    {
                        label1.Text = i.ToString() + "秒経過";
                    }));
                    sum += i;
                    System.Threading.Thread.Sleep(1000);
                }
                return sum;
            });
            // タスクを開始する
            task.Start();
            // 算出処理終了後に表示処理を実行するタスクを生成する
            task.ContinueWith(t => 
            {
                int res = t.Result;
                this.Invoke(new Action(() => { 
                    label2.Text = "合計値:" + res.ToString();
                }));
            });

            return task;
        }

        private async void button1_Click(object sender, EventArgs e)
        {
            await onTask();
        }
Private Function onTask() As Task(Of Integer)
    Dim task = New Task(Of Integer)(Function()
                                        '合計値を毎秒計算する
                                        Dim sum As Integer = 0
                                        For i As Integer = 0 To 9
                                            Me.Invoke(New Action(Sub()
                                                                    Label1.Text = i.ToString() + "秒経過"
                                                                End Sub))
                                            sum += i
                                            System.Threading.Thread.Sleep(1000)
                                        Next
                                        Return sum
                                    End Function)
    'タスクを開始する
    task.Start()
    ' 算出処理終了後に表示処理を実行するタスクを生成する
    task.ContinueWith(Sub(t)
                         Dim res As Integer = t.Result
                         Me.Invoke(New Action(Sub()
                                                  Label2.Text = "合計値:" & res.ToString()
                                              End Sub))
                     End Sub)

    Return task
End Function

Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Await onTask()
End Sub

このプログラムでは、onTask関数でタスクを生成し、それを Start メソッドで開始しています。その後、ContinueWith メソッドを使って、タスク完了時に続けて処理を実行しています。また、button1_Click イベントハンドラ内では、await キーワードを使って、onTask 関数が完了するのを待っています。

実行中のタスクをキャンセルする(CancellationTokenSource)

 実行中のタスクをキャンセルするにはCancellationTokenSourceクラスを利用します。

 タスク生成時に、引数で「CancellationTokenSource」のTokenプロパティを渡し、タスク実行中にCancelメソッドを呼び出すことで実行中のタスクを停止させることができます。

以下のサンプルは、1つ目のボタンをクリックすると、10秒かけてラベルに0から9までの数字を表示します。2つ目のボタンをクリックすると、このタスクをキャンセルします。

        CancellationTokenSource cts = new CancellationTokenSource();
        private async void button1_Click(object sender, EventArgs e)
        {
            var res = await Task.Factory.StartNew(() =>
            {
                for(int i = 0; i < 10; i++)
                {
                    if (cts.Token.IsCancellationRequested) { return false; }
                    this.Invoke(new Action(() => 
                    { 
                        label1.Text = i.ToString() + "秒経過"; 
                    }));
                    System.Threading.Thread.Sleep(1000);
                }
                return true;
            }, cts.Token);

        }
        private void button2_Click(object sender, EventArgs e)
        {
            cts.Cancel();
        }
//以下がインポートされているものとする
//Imports System.Threading  
//Imports System.Threading.Tasks

        Dim cts As CancellationTokenSource = New CancellationTokenSource()
        Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            Dim res = Await Task.Factory.StartNew(Function()
                                                     For i As Integer = 0 To 9
                                                         If cts.Token.IsCancellationRequested Then Return False
                                                         Me.Invoke(New Action(Sub()
                                                                                  Label1.Text = i.ToString() & "秒経過"
                                                                              End Sub))
                                                         System.Threading.Thread.Sleep(1000)
                                                     Next
                                                     Return True
                                                 End Function, cts.Token)
        End Sub
        Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
            cts.Cancel()
        End Sub

リスキリングでキャリアアップしてみませんか?

リスキリング(学び直し)は、経済産業省が推奨しており、

今だけ、最大70%のキャッシュバックを受けることができます。

リスキリング 給付金が出るスクール紹介

最大70%の給付金が出るおすすめのプログラミングスクール!

国策で予算が決められているため申し込みが多い場合は早期に終了する可能性があります!

興味のある方はすぐに確認しましょう。

これで非同期処理のプログラムも怖くありません!

非同期処理を利用することで、プログラムよってはパフォーマンスを大幅に向上させることができるので、必要に応じて積極的に利用しましょう。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次