パラメーターがあり、戻り値がある場合の非同期関数を利用した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.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace SimpleTask
{
public partial class FormTaskFunctionParam : Form
{
public FormTaskFunctionParam()
{
InitializeComponent();
}
private async void button2_Click(object sender, EventArgs e)
{
int a = 9, b = 8, c = 7, d = 6;
List<int> inparam1 = new List<int>();
inparam1.Add(a);
inparam1.Add(b);
List<int> inparam2 = new List<int>();
inparam2.Add(c);
inparam2.Add(d);
int p1_result = await Task.Run<int>(() => proc(inparam1));
textBox1.Text = p1_result.ToString();
int p2_result = await Task.Run<int>(() => proc(inparam2));
textBox1.Text = p1_result.ToString() + ", " + p2_result.ToString();
}
private int proc(object param)
{
System.Threading.Thread.Sleep(2000);
List<int> inList = (List<int>)param;
int result = 0;
foreach (int i in inList) {
result += i;
}
return result;
}
}
}
解説
Runメソッドのラムダ式には引数のないデリゲートを与えますが、ラムダ式の右辺での処理メソッド呼び出し時に、呼び出し元のパラメーターの変数を与えることで、
Taskオブジェクトにパラメーターを渡しています。~
List<int> inparam1 = new List<int>();
inparam1.Add(a);
inparam1.Add(b);
int p1_result = await Task.Run<int>(() => proc(inparam1));
実行結果
プロジェクトを実行します。下図のウィンドウが表示されます。
[button2]をクリックします。2秒ほどすると画面に変数
a
と
b
の和がテキストボックスに表示されます。表示されるまでの間、
メインスレッドはブロックされないため、ウィンドウの操作などはできることが確認できます。
さらに2秒ほど経過すると、変数
c
と
d
の和がテキストボックスに表示されます。結果が表示されるまでの間
メインスレッドはブロックされないので、ウィンドウの操作などでき、UIはフリーズしません。
プログラム2 : Task.Factory.StartNew を利用する
Task.Run メソッドは戻り値が void 型のためawaitで待機できませんが、Task.Factory.StartNew メソッドを利用すると戻り値を受け取れるため、awaitで待機できます。
UI
下図のUIを作成します。今回のプログラムでは [button3]のみを利用します。
コード
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace SimpleTask
{
public partial class FormTaskFunctionParam : Form
{
public FormTaskFunctionParam()
{
InitializeComponent();
}
private async void button3_Click(object sender, EventArgs e)
{
int a = 9, b = 8, c = 7, d = 6;
List<int> inparam1 = new List<int>();
inparam1.Add(a);
inparam1.Add(b);
List<int> inparam2 = new List<int>();
inparam2.Add(c);
inparam2.Add(d);
int p1_result = await Task.Factory.StartNew(proc, inparam1);
textBox1.Text = p1_result.ToString();
int p2_result = await Task.Factory.StartNew(proc, inparam2);
textBox1.Text = p1_result.ToString() + ", " + p2_result.ToString();
}
private int proc(object param)
{
System.Threading.Thread.Sleep(2000);
List<int> inList = (List<int>)param;
int result = 0;
foreach (int i in inList) {
result += i;
}
return result;
}
}
}
解説
Task.Factory.StartNew()
メソッドを呼び出して処理を実行します。第一引数に実行する処理のメソッド(デリゲート)を与えます。第二引数にパラメーターを与えます。
戻り値は
Task<int>
型ですが、awaitを記述して呼び出す場合は戻り値は
int
で受け取れます。
int p1_result = await Task.Factory.StartNew(proc, inparam1);
実行結果
プロジェクトを実行します。下図のウィンドウが表示されます。
[button3]をクリックします。2秒ほどすると画面に変数
a
と
b
の和がテキストボックスに表示されます。表示されるまでの間、
メインスレッドはブロックされないため、ウィンドウの操作などはできることが確認できます。
さらに2秒ほど経過すると、変数
c
と
d
の和がテキストボックスに表示されます。結果が表示されるまでの間
メインスレッドはブロックされないので、ウィンドウの操作などでき、UIはフリーズしません。
プログラム3 : Task.Run を利用しないコード
Task.Runを利用しないで非同期関数を実行するコードを紹介します。
戻り値のない void 関数は待機できませんので、Runメソッドを利用せずに非同期関数を実行して待機する場合には、単純に非同期関数を呼び出す処理になります。
この場合は戻り値がvoid の関数ではなく Task 型の戻り値を返す関数を記述して利用します。
UI
下図のUIを作成します。今回のプログラムでは [button4]のみを利用します。
コード
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace SimpleTask
{
public partial class FormTaskFunctionParam : Form
{
public FormTaskFunctionParam()
{
InitializeComponent();
}
private async void button4_Click(object sender, EventArgs e)
{
int a = 9, b = 8, c = 7, d = 6;
List<int> inparam1 = new List<int>();
inparam1.Add(a);
inparam1.Add(b);
List<int> inparam2 = new List<int>();
inparam2.Add(c);
inparam2.Add(d);
int p1_result = await proc_a(inparam1);
textBox1.Text = p1_result.ToString();
int p2_result = await proc_a(inparam2);
textBox1.Text = p1_result.ToString() + ", " + p2_result.ToString();
}
private async Task<int> proc_a(List<int> param)
{
await Task.Delay(3000);
int result = 0;
foreach (int i in param) {
result += i;
}
return result;
}
}
}
解説
非同期関数として実装する場合、戻り値の型を
Task<戻り値の型>
とします。元の関数がint型の戻り値の関数でしたので、
戻り値の型を
Task<int>
とします。
private async Task<int> proc_a(List<int> param)
{
await Task.Delay(3000);
int result = 0;
foreach (int i in param) {
result += i;
}
return result;
}
非同期関数の呼び出しは、awaitを付けて関数を呼び出す記述です。非同期関数の戻り値は
Task<int>
ですが、awaitを付けて呼び出した場合の戻り値は
int
で受け取れます。
int p1_result = await proc_a(inparam1);
textBox1.Text = p1_result.ToString();
int p2_result = await proc_a(inparam2);
textBox1.Text = p1_result.ToString() + ", " + p2_result.ToString();
実行結果
プロジェクトを実行します。下図のウィンドウが表示されます。
[button4]をクリックします。2秒ほどすると、テキストボックスに変数
a
と
b
の和が表示されます。表示されるまでの間メインウィンドウがフリーズしないことが確認できます。
テキストボックスに表示されてから、さらに2秒ほど経過すると、テキストボックスに変数
c
と
d
の和が表示されます。表示されるまでの間、メインウィンドウはフリーズしません。
エラーになる例 : 単純に async / await を追加した場合
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace SimpleTask
{
public partial class FormTaskFunctionParam : Form
{
public FormTaskFunctionParam()
{
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e)
{
int a = 9, b = 8, c = 7, d = 6;
List<int> inparam1 = new List<int>();
inparam1.Add(a);
inparam1.Add(b);
Task<int> task1 = new Task<int>(proc, (object)inparam1);
List<int> inparam2 = new List<int>();
inparam2.Add(c);
inparam2.Add(d);
Task<int> task2 = new Task<int>(proc, (object)inparam2);
await task1.Start();
await task2.Start();
int result = task1.Result + task2.Result;
textBox1.Text = result.ToString();
}
private int proc(object param)
{
System.Threading.Thread.Sleep(2000);
List<int> inList = (List<int>)param;
int result = 0;
foreach (int i in inList) {
result += i;
}
return result;
}
}
}
解説
非同期での呼び出しにするため、呼び出し元のbutton1_Clickメソッドに asyncを追記しています。また、 タスクの開始時のStart() メソッドの呼び出し時に await を追記しています。 こちらのコードですが、コンパイルすると下記のコンパイルエラーが発生します。
エラー CS4008 void' を待機することができません
Task.Start() メソッドは戻り値のない void のため上記のエラーが発生します。
著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
最終更新日: 2021-08-24
作成日: 2020-02-07