佐々木屋

技術的なことから趣味まで色々書きます

非同期処理、マルチスレッド(async/await)

非同期処理最終話です。

.Net Framework4.0でTaskクラスが登場し、非同期処理がより簡単に書けるようになりましたが、.Net Framework4.5からTaskクラスをもっと使いやすくしたasync/awaitが登場します。
非同期処理はThreadから始まり、Timerスレッドやデリゲート、ThreadPool等色々やり方を解説してきましたが、現時点でasync/awaitが最も主流で有用な書き方になります。

例として、以下の同期処理を考えます。
単純に6秒間待つような処理です。

private void button1_Click(object sender, EventArgs e) {
    Thread.Sleep(6000);
    Console.WriteLine("完了");
}
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Thread.Sleep(6000)
    Console.WriteLine("完了")
End Sub

この処理をasync/awaitを使った非同期処理に変更していきます。
簡単ですが、先にasync/awaitの機能の説明をしておきます。

async修飾子

メソッドの頭にasync修飾子をつけると非同期メソッドになります。非同期メソッドは通常のメソッドと異なり、awaitキーワードが使用できるようになります。

awaitキーワード

awaitキーワードで指定された処理は非同期で行われます。awaitキーワードで指定された処理の後は非同期処理が完了した後で処理されますので、非同期処理を安全に行うことが可能です。また、処理の結果を取り出すことも可能です。


これだけです。たったこれだけで今まで煩わしかったスレッドの管理やコールバックの利用などが全て不要となります。

非同期処理を書いてみよう

さて、例のコードを非同期メソッドを利用して変更してみます。

private async void button1_Click(object sender, EventArgs e) {
    await Task.Run(()=> Thread.Sleep(6000));
    Console.WriteLine("完了");
}
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Await Task.Run(Sub()
                     Thread.Sleep(6000)
                   End Sub)
    Console.WriteLine("完了")
End Sub

どうでしょうか。最初の例のコードに比べて、awaitのTask.Runメソッドが加わっただけで、構成としてほとんど変わっていないのです。このようにasync/awaitを利用した非同期処理の一番のメリットは、同期処理と同じ流れで非同期処理を書くことができ、様々なところでコストがかからないことです。


もう一つ例をあげてみましょう。
ボタンを押下すると、HeavyProcメソッドに何か適当な文字列を渡して6秒間スリープさせ、結果を受け取るといった単純な処理を考えます。
ただ結果を受け取るだけではつまらないので、適当にテキストボックスを作ってその入力内容をHeavyProcメソッドに渡します。

同期処理では当然ボタンを押下した瞬間に固まったようになりますので、その処理が終わらない限り次の入力は出来なくなります。

private string HeavyProc(string naiyo) {
    for (int i = 0; i <= 5; ++i) {
        Thread.Sleep(1000));
        Console.WriteLine(naiyo);
    }
    return naiyo + ":完了";
}
private void button1_Click(object sender, EventArgs e) {
    string res = HeavyProcAsync(textBox1.Text);
    Console.WriteLine(res);
}
Public Function HeavyProc(ByVal naiyo As String) As String
    For i As Integer = 0 To 5
        Thread.Sleep(1000)
        Console.WriteLine(naiyo)
    Next
    Return naiyo & ":完了"
End Function
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Dim res As String = HeavyProc(TextBox1.Text)
    Console.WriteLine(res)
End Sub



早速async/awaitを付けて非同期処理にしてみます。
なお、慣例として非同期メソッドはメソッド名の最後に「Async」を付けます。ただこれを付けなかったからといって動作が変わるとかはありません。あくまで慣例です。

public async Task<string> HeavyProcAsync(string naiyo) {
    for (int i = 0; i <= 5; ++i) {
        await Task.Run (()=>Thread.Sleep(1000));
        Console.WriteLine(naiyo);
    }
    return naiyo + ":完了";
}
private async void button1_Click(object sender, EventArgs e) {
    string res = await HeavyProcAsync(textBox1.Text);
    Console.WriteLine(res);
}
Public Async Function HeavyProcAsync(ByVal naiyo As String) As Task(Of String)
    For i As Integer = 0 To 5
        Await Task.Run(Sub()
                         Thread.Sleep(6000)
                       End Sub)
        Console.WriteLine(naiyo)
    Next
    Return naiyo & ":完了"
End Function
Private Async Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
    Dim res As String = Await HeavyProcAsync(TextBox1.Text)
    Console.WriteLine(res)
End Sub

結果はどうなりましたか?


これで非同期処理・マルチスレッドの話は一旦完了となります。

非同期処理、マルチスレッドは個人的に非常に好きで、楽しくまとめることができましたが、少々「クセ」があるので難しかったかもしれません。少しでもこの面白さが伝われば幸いです。