としおの読書生活

田舎に住む社会人の読書記録を綴ります。 主に小説や新書の内容紹介と感想を書きます。 読書の他にもワイン、紅茶、パソコン関係などの趣味を詰め込んだブログにしたいです。

タグ:スレッド

3753817_s

今回はC#の非同期処理を理解するためにawaitとasyncについて勉強していきます。


asyncとawaitとは


asyncとawaitとはC#5.0以降から追加された機能です。

asyncとawaitを使うことで実装が面倒な非同期処理をシングルスレッドを書くかのような感じで実装することができます。

awaitとは文字通り待機するという意味です。

awaitを付けた場合その処理は一度別スレッドで実行されますが、その処理が終了するまでメインスレッドも待機したまま処理が進みません。

asyncはawaitを使用する際にメソッドにつけるおまじないのようなものです。

asyncとawaitを使ったメソッドは以下のようなものになります。

static async Task Main()
{
    await TaskA();
}



awaitを使って非同期処理が終わるまで待機する


それではawaitを使って非同期処理が終了するまでメインスレッドが待機する処理を実装してみます。

using System;
using System.Threading.Tasks;

class AsyncSample
{
    static async Task Main()
    {
        Console.WriteLine("Start");
        await AsyncProcess();
        Console.WriteLine("End");
    }

    private static async Task AsyncProcess()
    {
        await Task.Run(() =>
        {
            for (int i = 0i < 1000; ++i)
            {
                Console.WriteLine(i);
            }
        });
    }
}


実行結果は以下の通りです。

Start
0
1
2
3
~
999
End

実行結果から見て分かる通りAcyncProcessメソッドの処理が終了するまでMainメソッドの処理が進んでいませんね。





非同期処理を待たない


先ほどはawaitを使って非同期処理が終わるまで待機しましたが時と場合によっては、せっかく非同期処理をしているので処理が終わるのをまたずにメインスレッドを進めたい場合もあります。

そういうときはawaitを付けなければいいだけです。

先ほどのサンプルからMainメソッドを以下のように変更してみます。

static async Task Main()
{
    Console.WriteLine("Start");
    AsyncProcess();
    Console.WriteLine("End");
}

実行結果は以下の通りです。

Start
0
1
2
3
~
18
End

AcyncProcessメソッドの処理が終了するのを待たずにMainメソッドの処理が進んでいますね。

awaitを使わない場合、デッドロックにならないように注意する必要があります。

複数のスレッドで同じ変数を変更したり、参照していたりするとデッドロックが起きる可能性がありますので注意しましょう。



まとめ


今回はawaitとasyncの基本的な使用方法について学んでいきました。

awaitとasyncのおかげでC#は手軽に非同期処理が実装できるので楽ですね。

次はasyncをつけたメソッドの戻り値について勉強していきたいです。

asyncを付けたメソッドから戻り値を受け取る方法について調べました。








3753817_s

C#の非同期処理でawaitとasyncを使ったときに戻り値を受け取る方法を調べたので忘備録として記録していきます。

async/awaitの基本的な使い方は以下にまとめています。




戻り値なしの場合


戻り値なしの場合は、voidまたはTaskを戻り値として設定します。

ただ、voidを設定した場合はawaitを使って待つことはできません。

static async Task Main()
{
    Console.WriteLine("Start");
    AsyncProcess();
    Console.WriteLine("End");
}
    
private static async void AsyncProcess()
{
    await Task.Run(() =>
    {
        for (int i = 0i < 1000; ++i)
        {
            Console.WriteLine(i);
        }
    });
}

そのため非同期処理でしか呼びだないような関数の場合はTaskを使った方がよさそうです。

非同期処理でvoidを使うメリットはあまりありませんね。

static async Task Main()
{
    Console.WriteLine("Start");
    AsyncProcess();
    await AsyncProcess();
    Console.WriteLine("End");
}







戻り値を受け取りたい場合


戻り値を受け取りたい場合は、戻り値の方にTask<T>を設定します。

Tにはintやstringのように受け取りたい戻り値の方が入り、Task<int>、Task<string>のように書きます。

ただ、呼び出し元や呼び出し先では型がTask<T>であることを意識しなくても大丈夫です。

Task<int>の場合はint型を戻り値として扱うのと同じように書くことができます。

サンプルは以下の通りです。

static async Task Main()
{
    int nRet = await retInt();
    Console.WriteLine(nRet);

    string strRet = await retStr();
    Console.WriteLine(strRet);
}
    
private static async Task<intretInt()
{
    int n = 0;
    await Task.Run(() =>
    {
        for (int i = 0i < 10; ++i)
        {
            n += i;
        }
    });
    return n;
}

private static async Task<stringretStr()
{
    int n = 0;
    await Task.Run(() =>
    {
        for (int i = 0i < 10; ++i)
        {
            n += i;
        }
    });
    return n.ToString();
}

非同期処理で戻り値を受け取ることができるのはawaitで待つ時だけなので注意しましょう。

awaitを使わずに関数の値を取得したい場合はメンバ変数とかに入れるのがよさそうですね。



まとめ


今回はasync/awaitを使ったときに戻り値を受け取る方法を学びました。

戻り値の方としてTask<T>を使うことさえ忘れなければ簡単に使えますね。






↑このページのトップヘ