非同期関数でパラメーターが無く戻り値のある Task (Task<T>) の利用 - C#

パラメーターが無く、戻り値がある場合の非同期関数を利用したTaskの利用について紹介します。

概要

こちらの記事では、Taskを利用して別スレッドで処理を実行する例を紹介しました。この記事では、Taskで処理するメソッドを非同期関数にするコードを紹介します。

プログラム1 : Task.Run メソッドを利用する

void関数が待機できない対策として、Runメソッドを利用する方法があります。
Windows Formアプリケーションを作成します。

UI

下図のUIを作成します。今回のプログラムでは [button2]のみを利用します。

コード

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace SimpleTask
{
  public partial class FormAsyncTaskFunction : Form
  {
    public FormAsyncTaskFunction()
    {
      InitializeComponent();
    }

    private async void button2_Click(object sender, EventArgs e)
    {
      string t1_result = await Task<string>.Run<string>(proc1);
      textBox1.Text = t1_result;

      string t2_result = await Task<string>.Run<string>(proc2);
      textBox1.Text = t1_result + "," + t2_result;
    }

    private string proc1()
    {
      System.Threading.Thread.Sleep(2000);
      return "ぺんぎん";
    }

    private string proc2()
    {
      System.Threading.Thread.Sleep(3000);
      return "あひる";
    }
  }
}

解説

こちらの記事で紹介した、Runメソッドを利用してTaskオブジェクトの処理を実行するコードを非同期化したコードになります。
button2_Click メソッドには await を追記して非同期関数にします。Task.Run メソッドを利用してタスクの処理を実行します。await を追記して処理を待つ動作にします。
戻り値はTask内で実行される関数、proc1, proc2 の戻り値の型が返ります。Task自体の型は Task<戻り値の型> となりますので、Runメソッドの呼び出しは Task<戻り値の型>.Run<戻り値の型> となります。
また、Task.Run メソッドの引数にラムダ式を与える記述方法でも同じ動作になります。
    private async void button2_Click(object sender, EventArgs e)
    {
      string t1_result = await Task<string>.Run<string>(() => proc1());
      textBox1.Text = t1_result;

      string t2_result = await Task<string>.Run<string>(() => proc2());
      textBox1.Text = t1_result + "," + t2_result;
    }

型を明示しない下記の書式でも正しく実行されます。

ラムダ式を与えない記述

  private async void button2_Click(object sender, EventArgs e)
  {
    string t1_result = await Task<string>.Run<string>(proc1);
    textBox1.Text = t1_result;

    string t2_result = await Task<string>.Run<string>(proc2);
    textBox1.Text = t1_result + "," + t2_result;
  }
  private async void button2_Click(object sender, EventArgs e)
  {
    string t1_result = await Task<string>.Run(proc1);
    textBox1.Text = t1_result;

    string t2_result = await Task<string>.Run(proc2);
    textBox1.Text = t1_result + "," + t2_result;
  }
  private async void button2_Click(object sender, EventArgs e)
  {
    string t1_result = await Task.Run<String>(proc1);
    textBox1.Text = t1_result;

    string t2_result = await Task.Run<String>(proc2);
    textBox1.Text = t1_result + "," + t2_result;
  }
  private async void button2_Click(object sender, EventArgs e)
  {
    string t1_result = await Task.Run(proc1);
    textBox1.Text = t1_result;

    string t2_result = await Task.Run(proc2);
    textBox1.Text = t1_result + "," + t2_result;
  }

ラムダ式を与える記述

  private async void button2_Click(object sender, EventArgs e)
  {
    string t1_result = await Task<string>.Run<string>(() => proc1());
    textBox1.Text = t1_result;

    string t2_result = await Task<string>.Run<string>(() => proc2());
    textBox1.Text = t1_result + "," + t2_result;
  }
  private async void button2_Click(object sender, EventArgs e)
  {
    string t1_result = await Task<string>.Run(() => proc1());
    textBox1.Text = t1_result;

    string t2_result = await Task<string>.Run(() => proc2());
    textBox1.Text = t1_result + "," + t2_result;
  }
  private async void button2_Click(object sender, EventArgs e)
  {
    string t1_result = await Task.Run<string>(() => proc1());
    textBox1.Text = t1_result;

    string t2_result = await Task.Run<string>(() => proc2());
    textBox1.Text = t1_result + "," + t2_result;
  }
  private async void button2_Click(object sender, EventArgs e)
  {
    string t1_result = await Task.Run(() => proc1());
    textBox1.Text = t1_result;

    string t2_result = await Task.Run(() => proc2());
    textBox1.Text = t1_result + "," + t2_result;
  }

実行結果

上記のプロジェクトを実行します。下図のウィンドウが表示されます。[button2]をクリックします。


ボタンクリックから2秒経過すると proc1の戻り値である "ぺんぎん" がテキストボックスに表示されます。


さらに3秒経過すると、proc2 の戻り値である "あひる" の文字列もテキストボックスに表示されます。
awaitによりproc1の終了まで待機し、その後proc2が実行されていることが確認できます。

プログラム2 : Runメソッドを利用して、処理部のメソッドをインラインで記述する

先のプログラムは、クラスに実装されているメソッドを指定してTaskを実行していますが、1度限りの処理などの場合は、メソッドでの記述よりもインラインで記述したい場合があります。
ラムダ式を利用すると、処理部をインラインで記述できます。

UI

Windows Form アプリケーションを作成し、下図のフォームを作成します。今回は[button3]のみを利用します。

コード

以下のコードを記述します。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace SimpleTask
{
  public partial class FormAsyncTaskFunction : Form
  {
    public FormAsyncTaskFunction()
    {
      InitializeComponent();
    }

    private async void button3_Click(object sender, EventArgs e)
    {
      Func<Task<string>> proc1_inl = async () =>
      {
        await Task.Delay(2000);
        return "ぺんぎん";
      };

      Func<Task<string>> proc2_inl = async () =>
      {
        await Task.Delay(3000);
        return "あひる";
      };

      string t1_result = await Task.Run(proc1_inl);
      textBox1.Text = t1_result;

      string t2_result = await Task.Run(proc2_inl);
      textBox1.Text = t1_result + "," + t2_result;
    }
  }
}

解説

非同期関数を記述しますので、戻り値の型はTask<戻り値型>となります。今回の関数はstring型を返すためTask<string>となります。
関数内では2秒間処理を止めたのち、"ぺんぎん" の文字列を結果として返します。
proc2_inl メソッドも同様の処理をしますが、処理の停止時間と結果の戻り値の文字列が proc1_inl と異なります。

  Func<Task<string>> proc1_inl = async () =>
  {
    await Task.Delay(2000);
    return "ぺんぎん";
  };
Task.Runメソッド呼び出し部分は、以下のどの記述でも動作します。
  string t1_result = await Task.Run(proc1_inl);
  string t2_result = await Task.Run(proc2_inl);
  string t1_result = await Task.Run<string>(proc1_inl);
  string t2_result = await Task.Run<string>(proc2_inl);
  string t1_result = await Task<string>.Run(proc1_inl);
  string t2_result = await Task<string>.Run(proc2_inl);
  string t1_result = await Task<string>.Run<string>(proc1_inl);
  string t2_result = await Task<string>.Run<string>(proc2_inl);

一つめは、Runメソッドの引数にラムダ式を与える記述方法も使用できます。
  string t1_result = await Task.Run(() => proc1_inl());
  string t2_result = await Task.Run(() => proc2_inl());
  string t1_result = await Task.Run<string>(() => proc1_inl());
  string t2_result = await Task.Run<string>(() => proc2_inl());
  string t1_result = await Task<string>.Run(() => proc1_inl());
  string t2_result = await Task<string>.Run(() => proc2_inl());
  string t1_result = await Task<string>.Run<string>(() => proc1_inl());
  string t2_result = await Task<string>.Run<string>(() => proc2_inl());

または、Runメソッドを使わずに デリゲートの呼び出しで、直接メソッドを呼び出す方法も可能です。
  string t1_result = await proc1_inl();
  textBox1.Text = t1_result;

  string t2_result = await proc2_inl();
  textBox1.Text = t1_result + "," + t2_result;

実行結果

上記のプロジェクトを実行します。下図のウィンドウが表示されます。[button2]をクリックします。


ボタンクリックから2秒経過すると proc1の戻り値である "ぺんぎん" がテキストボックスに表示されます。


さらに3秒経過すると、proc2 の戻り値である "あひる" の文字列もテキストボックスに表示されます。
awaitによりproc1の終了まで待機し、その後proc2が実行されていることが確認できます。

プログラム3 : Runメソッドを利用せずに、非同期関数を実行する

単純に非同期関数を呼び出す処理でも同様の動作が記述できます。この場合は戻り値がTask<戻り値の型>型になります。

UI

コード

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace SimpleTask
{
  public partial class FormAsyncTaskFunction : Form
  {
    public FormAsyncTaskFunction()
    {
      InitializeComponent();
    }

    private async void button4_Click(object sender, EventArgs e)
    {
      string result_a = await procA();
      string result_b = await procB();
    }

    private async Task<string> procA()
    {
      await Task.Delay(2000);
      return "ぺんぎん";
    }

    private async Task<string> procB()
    {
      await Task.Delay(3000);
      return "あひる";
    }
  }
}

解説

待機できる非同期関数を作成するには、メソッドに async キーワードを追記するとともに、Task<戻り値の型>型の戻り値を返す関数に変更します。 この記述で待機可能な非同期関数が実装できます。
関数では、Task<戻り値の型>が戻り値となりますが、コード上は 戻り値の型 を返す記述でよいです。今回のコードでは、戻り値の型はTask<string>ですが return文で返す値はstring型の値となります。

呼び出し元では await キーワードを追記して非同期メソッドを呼び出すことで、メソッドの完了まで待機できます。

実行結果

上記のプロジェクトを実行します。下図のウィンドウが表示されます。[button2]をクリックします。


ボタンクリックから2秒経過すると proc1の戻り値である "ぺんぎん" がテキストボックスに表示されます。


さらに3秒経過すると、proc2 の戻り値である "あひる" の文字列もテキストボックスに表示されます。
awaitによりprocAの終了まで待機し、その後procBが実行されていることが確認できます。

エラーになる例 : 単純に async / await を追加した場合

こちらの記事の同期呼び出しのコードをもとに、下記のコードに書き換えます。

コード

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace SimpleTask
{
  public partial class FormAsyncTaskFunction : Form
  {
    public FormAsyncTaskFunction()
    {
      InitializeComponent();
    }

    private async void button1_Click(object sender, EventArgs e)
    {
      Task<string> task1 = new Task<string>(proc1);
      Task<string> task2 = new Task<string>(proc2);

      await task1.Start();
      await task2.Start();

      textBox1.Text = task1.Result + "," + task2.Result;
    }

    private string proc1()
    {
      System.Threading.Thread.Sleep(2000);
      return "ぺんぎん";
    }

    private string proc2()
    {
      System.Threading.Thread.Sleep(3000);
      return "あひる";
    }
  }
}

解説

非同期での呼び出しにするため、呼び出し元のbutton1_Clickメソッドに asyncを追記しています。また、 タスクの開始時のStart() メソッドの呼び出し時に await を追記しています。 こちらのコードですが、コンパイルすると下記のコンパイルエラーが発生します。
エラー CS4008 void' を待機することができません
Task.Start() メソッドは戻り値のない void のため上記のエラーが発生します。

著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
最終更新日: 2022-01-08
作成日: 2020-02-07
iPentec all rights reserverd.