残り僅かな全盛期

30代修行中の初心者プログラマー。いろいろなサイトを参考にコードを使用させてもらいます。

非同期処理のいろいろ

初心者プログラマーにとって、
非同期処理はとてもハードルが高い。
方法が一つだけならまだしも、
いろいろな手法があって自分の中でなかなか整理できない。

ということで実際にコードを書いてまとめてみた。

参考にした本は、これ。
gihyo.jp

参考書の中ではWindowsフォーム用のコードが書かれていたので、
WPF用に少し修正した。

まず、Threadクラスを使用した非同期処理。

private string _labelText;

public string LabelText
{
    get
    {
        return _labelText;
    }
    set
    {
        Set(ref _labelText, value, "LabelText");
    }
}

private RelayCommand _buttonClickCommand;

public RelayCommand ButtonClickCommand
{
    get
    {
        return _buttonClickCommand ?? (_buttonClickCommand = new RelayCommand(OnButtonClick));
    }
}

public void OnButtonClick()
{
    LabelText = "開始";
    //Threadクラスを使用した非同期処理
    var th = new Thread(DoLongSomething);
    th.Start();
}

private void DoLongSomething()
{
    Thread.Sleep(5000);
    LabelText = "終了";
}

簡単に説明をすると、View側にボタンとラベルを配置して、
ボタン開始時にラベルのテキストを変更し、
5秒待機した後、再度ラベルのテキストを変更するというもの。
Viewには別のコントロールも配置していて、
5秒待機している間、UIがフリーズすることなくコントロールを操作できるかを確認している。
この全体の動作はこのあとに登場するコードでも同様。

続いて、Taskクラスを使用した非同期処理。

public void OnButtonClick()
{
    LabelText = "開始";
    //Taskクラスを使用した非同期処理
    Task.Run(() => DoLongSomething());
}

private void DoLongSomething()
{
    Thread.Sleep(5000);
    LabelText = "終了";
}

次に、await/asyncを使用し、戻り値がないメソッドを呼ぶ。
ポイントは、OnButtonClickメソッドにsysnc修飾子をつけること。
asyncをつけることでawaitが使用可能になり、
awaitをつけたTaskはそのタスクが完了するまで待機する。

public async void OnButtonClick()
{
    LabelText = "開始";
    //awaitを使用した非同期処理
    await Task.Run(() => DoLongSomething());

    //書き方違うバージョン
    await Task.Run(() =>
    {
        DoLongSomething();
    });

    //分割して書くと以下
    Task task = Task.Run(() => DoLongSomething());
    await task;

    LabelText = "終了";
}

private void DoLongSomething()
{
    Thread.Sleep(5000); 
}

await/asyncを使用し、戻り値があるメソッドを呼ぶ。

public async void OnButtonClick()
{
    LabelText = "開始";
    //戻り値のあるメソッドを非同期処理
    var elapsed = await Task.Run(() => DoLongSomething());
    LabelText = $"{elapsed}ミリ秒";
}

/// <summary>
/// 戻り値のある同期メソッド
/// </summary>
/// <returns></returns>
private long DoLongSomething()
{
    var sw = Stopwatch.StartNew();
    Thread.Sleep(5000);
    sw.Stop();
    return sw.ElapsedMilliseconds;
}

今度は、メソッド自体を非同期メソッドに。戻り値がないパターン。
ポイントは、DoLongSomethingをDoLongSomethingAsyncにして、
async修飾子をつけ、戻りの型をTaskにする。
>|cs|
public async void OnButtonClick()
{
    LabelText = "開始";
    //戻り値の無い非同期処理メソッドを呼ぶ
    await DoLongSomethingAsync();
    LabelText = "終了";
}

/// <summary>
/// 戻り値の無い非同期メソッド
/// </summary>
/// <returns></returns>
private async Task DoLongSomethingAsync()
{
    await Task.Run(() => Thread.Sleep(5000));
}

戻り値がある非同期メソッド。
DoLongSomethingAsyncの戻りの型はTaskとなる。

public async void OnButtonClick()
{
    LabelText = "開始";
    //戻り値のある非同期処理メソッドを呼ぶ
    var elapsed = await DoLongSomethingAsync();
    LabelText = $"{elapsed}ミリ秒";
}

/// <summary>
/// 戻り値のある非同期メソッド
/// </summary>
/// <returns></returns>
private async Task<long> DoLongSomethingAsync()
{
    var sw = Stopwatch.StartNew();
    await Task.Run(() => Thread.Sleep(5000));
    sw.Stop();
    return sw.ElapsedMilliseconds;
}