Task(またの名をすっどれ)

今日はC#ですっどれを使う練習。
すっどれと言っても、2chのあれではない(このネタは前にもやったが)。
1つのプログラムで、あたかも複数のプロセスが同時・並列処理されるように見える、あれだ。
最近の高度なフレームワーク(.netやJVMなど)では、いちいちスレッドを自分で起こすようなことはあまりしない。
スレッドを起こすということは、新たな実行空間を準備するということであり、それはただ単にメモリーを確保するということだけではなく、タスクスイッチなどの実行管理、タスク間通信のフレームワークなどいろいろと面倒くさく、OSから見ても非常に負荷が高い。
なので最近の高度なフレームワークでは、事前にスレッドがいくつも起こしてあり、さらにそのスレッドの中でも複数のタスクが起動できるようになっている。


ということでC#では特別に必要ででない限り、.net frameworkですでに起こしてあるスレッドにタスクを放り込んで並行処理をする。
具体的には


Task tasktest = Task.Factory.StartNew(実行するメソッド);
とする。
これにラムダ式というインライン関数を組み合わせて、

Task taskTest = Task.Factory.StartNew(()=>
{
//ここでスレッド処理したい内容
});
とすれば、実行するメソッドをいちいち関数として起こす必要がない。


さてスレッドを起こせば、当然止める方法も必要になる、さもないと永遠に動作し続けてしまうこともあるからだ。
これにはCancellationTokenSourceを利用する。
こんな感じだ。


CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
Task taskTest = Task.Factory.StartNew(()=>
{
while (true)
{
try
{
cancellationTokenSource.Token.ThrowIfCancellationRequested();
}
catch(OperationCanceledException)
{
break;
}
//ここでスレッド処理したい内容
}
// cancellationTokenSourceを解放
cancellationTokenSource.Dispose();
}, cancellationTokenSource.Token);
これで外部からcancellationTokenSource.Cancelを呼び出せば無限ループが止まる。
もちろんcancellationTokenSource.Cancelをタスクを起こしたメソッド以外のメソッドから呼び出すためには、cancellationTokenSourceをクラスメンバにしておく必要がある。

以下のソースは、

  • タスクを起動、中断するためのボタン
  • カウンター表示とタスクの状況を表示するラベル

を配置したフォームのソースになる。
ソースを見やすくするために、故意にラムダ式を使用せず、明示的な関数(processTask)を起こしてある。
フォームに配置したラベルにはタスクから直接アクセスできないので、更新をするためのメソッドとそのデリゲートも実装されている。


namespace TaskTest
{
public partial class Form1 : Form
{
CancellationTokenSource cancellationTokenSource;

public Form1()
{
InitializeComponent();
//
labelCount.Text = "0";
labelIsRunning.Text = "stopped";
cancellationTokenSource = null;
}

private void onButton1Click(object sender, EventArgs e)
{
if (cancellationTokenSource == null) // if task is not running
{
cancellationTokenSource = new CancellationTokenSource();
Task taskTest = Task.Factory.StartNew(processTask, cancellationTokenSource.Token);
}
else
{
cancellationTokenSource.Cancel();
}
}

private delegate void DelegateUpdateLabel(string Message);

void updateLabelCount(string Message)
{
labelCount.Text = Message;
}

void updateLabelIsRunning(string Message)
{
labelIsRunning.Text = Message;
}

void processTask()
{
DelegateUpdateLabel delegateUpdateLabelCount = new DelegateUpdateLabel(updateLabelCount);
DelegateUpdateLabel delegateUpdateLabelIsRunning = new DelegateUpdateLabel(updateLabelIsRunning);
for (int count = 0; count < 10; count++)
{
// catch the cancellation
try
{
cancellationTokenSource.Token.ThrowIfCancellationRequested();
}
catch (OperationCanceledException oce)
{
break;
}
// main loop
this.Invoke(delegateUpdateLabelIsRunning, "running");
this.Invoke(delegateUpdateLabelCount, string.Format("{0}", count));
Thread.Sleep(500);
}
if (cancellationTokenSource.Token.IsCancellationRequested)
{
this.Invoke(delegateUpdateLabelIsRunning, "cancelled");
}
else
{
this.Invoke(delegateUpdateLabelIsRunning, "stopped");
}
// task end process
cancellationTokenSource.Dispose();
cancellationTokenSource = null;
}
}
}