C#でTaskQueue
時間がかかる仕事をどんどんキューに放り込んでいくと、自分が持てるキャパを使ってどんどん仕事をさばいていくよーなことがしたかったんです。
たとえば、キャパが3つあったら、仕事を「仕事、仕事、仕事」と積んであげると、同時に実行します。
「仕事、仕事、仕事、仕事」と4つ積むと、最初の3つが並列で走って、真っ先に手が開いたスレッドが最後の4つ目の仕事をします。
ようするに、タスクスケジューラーみたいなことがやりたかったんですよ。
ThreadPool はアプリ全体で使いまわすらしいので、役に立たたなそうなので自分で作りました。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace ConsoleApplication1 { public class TaskQueue : IDisposable { //仕事キュー private Queue<ThreadStart> _taskQueue = new Queue<ThreadStart>(); //仕事をするスレッド private Thread[] _threads; //仕事ができたら通知してくれるイベント private AutoResetEvent _notifyEvent = new AutoResetEvent(false); //この変数はロックだ。そしてコレを見てくれる君達もロックだ。 private object _lockObject = new object(); //スレッド群の生成 public TaskQueue(int threadCount) { this._threads = new Thread[threadCount]; for (int i = 0; i < threadCount; i++) { this._threads[i] = new Thread(this.threadRunning); this._threads[i].Start(); } } //一応、リソース解放時に停止命令を出すようにするよ。 public void Dispose() { this.stop(); } //スレッド群の停止 public void stop() { if (this._threads == null) { return; } int i; //終了のお知らせ for (i = 0; i < this._threads.Length; i++) { this._threads[i].Interrupt(); } //確実にとめる for (i = 0; i < this._threads.Length; i++) { this._threads[i].Join(); this._threads[i] = null; } this._threads = null; } //タスクを追加する public void addTask(ThreadStart inTask) { //タスクを追加 lock (this._lockObject) { this._taskQueue.Enqueue(inTask); } //飯ができたぞー this._notifyEvent.Set(); } //キューに残っている仕事の確認 public int Count { get { lock (this._lockObject) { return this._taskQueue.Count; } } } //スレッドの中で動作 private void threadRunning() { try { ThreadStart task; while (true) { //仕事が来るまで寝る。 ニートになる。リーマン乙。 this._notifyEvent.WaitOne(); while (true) { //何か仕事はないかな? lock (this._lockObject) { if (this._taskQueue.Count <= 0) { //仕事がないので寝る break; } //内定 task = this._taskQueue.Dequeue(); } //仕事をする リーマン状態 task(); //次の仕事を探しに行く。 } } } catch (ThreadInterruptedException) { //おしまい } } }; //ここから動作確認用のサンプルソース class Program { static void Main(string[] args) { //タスクキューの作成 最大3つまで同時動作 TaskQueue task = new TaskQueue(3); while (true) { System.Console.WriteLine("タスクキューサンプル"); System.Console.WriteLine(" タスクキュー3つまでなら同時に仕事をさばきます。"); System.Console.WriteLine(" スペースキーを押して仕事を積みます。"); System.Console.WriteLine("q 終了"); System.Console.WriteLine("スペース 仕事を積む"); System.Console.WriteLine("その他 画面の更新"); ConsoleKeyInfo key = Console.ReadKey(false); Console.Clear(); if (key.KeyChar == 'Q' || key.KeyChar == 'q') { System.Console.WriteLine("( ゚д゚)ぽかーん"); break; } if (key.KeyChar == ' ') { int time = (Environment.TickCount % 5000) + 1000; string name = (time % 3 == 0) ? "開発業務" : (time % 3 == 1) ? "資料作成" : "サポート業務"; System.Console.WriteLine("( ゚д゚)< この仕事やっといて、今日中に。"); //タスクの追加 task.addTask( delegate() { //なにか仕事をする Thread.Sleep(time); //終わったのでつぶやく。 System.Console.WriteLine("( ´∀`)< おわた。{0}ミリ秒かかる仕事({1})が終わりました。", time, name); }); System.Console.WriteLine("( ´∀`)< {0}ミリ秒かかる仕事({1})が積まれました", time, name); } //現在の仕事の数を表示. int workCount = task.Count; System.Console.WriteLine(""); System.Console.WriteLine("現在 {0} の仕事に手がつけられていません。", workCount); if (workCount > 5) { System.Console.WriteLine("( ´∀`)< ブラック乙"); } else if (workCount > 3) { System.Console.WriteLine("( ´∀`)< 帰れない。。。"); } else if (workCount > 2) { System.Console.WriteLine("( ´∀`)< これは、終電だな"); } else if (workCount > 0) { System.Console.WriteLine("( ´∀`)< タスクオーバー。残業だな"); } else if (workCount <= 0) { System.Console.WriteLine("( ´∀`)< キャパ内の仕事量です。"); } System.Console.WriteLine(""); System.Console.WriteLine(""); System.Console.WriteLine(""); System.Console.WriteLine(""); } //タスクキューに終了命令を出す task.stop(); } } }
んで、twitter にできたよーって書いたら、 @takeshik さんにガスガス修正いれられてしまった。
http://gist.github.com/436441
これがモダンな C# の書き方っすかー。