2014年08月24日

C#で並行処理

   このエントリーをはてなブックマークに追加 Clip to Evernote
どう書く?orgに感謝を込めて」シリーズ その35

■問題 (出題者:sumim さん)
数値(たとえば1から10)と、アルファベット(たとえばAからJまで)を順に出力する別々のループ処理を並行に実行させ、共通の出力先に出力する極力シンプルなコードを書いてください。
念のため、実行後、出力先に数値とアルファベットが混ざって出力されている(たとえば、数値がすべて出力されてからアルファベットが続く…というふうになっていない)ことを確認してください。混ざってさえいれば、それぞれ1文字ずつ交互である必要はありませんし、もちろん交互でも構いません。
出力先や出力方法は自由です。標準出力、テキストファイル、コンテナオブジェクト(配列、リスト、コレクション)など使いやすいもので構いません。
例として Squeak Smalltalk でのコードと結果を示します。シンプルなコードなので Smalltalk に馴染みがない人も、おおよその内容は掴めると思います。
| out |
out := OrderedCollection new.

[(1 to: 10) do: [:each | out add: each. Processor yield]] fork.
[($A to: $J) do: [:each | out add: each. Processor yield]] forkAndWait.

^out asArray  "=> #(1 $A 2 $B 3 $C 4 $D 5 $E 6 $F 7 $G 8 $H 9 $I 10 $J) "


もちろん可能であれば、疑似である必要はありません。^^;


出力先はいちばん簡単と思われる標準出力としました。
まずは、Threadクラスを使ったコード。

using System;
using System.Linq;
using System.Threading;
using System.Collections;
using System.Threading.Tasks;

namespace Doukaku.Org {
    class Program {
        static void Main(string[] args) {
            ParallelTest();
            Console.ReadLine();
        }

        // Threadを使った例
        private static void ParallelTest() {
            Thread th1 = new Thread(PrintSeq);
            Thread th2 = new Thread(PrintSeq);
            th1.Start("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
            th2.Start("abcdefghijklmnopqrstuvwxyz");
            th1.Join();
            th2.Join();
        }

        // 引き数 obj は、IEnumerableを実装している必要がある。引き数の値を順に出力する。
        // PrintSeqが直ぐに終わらないように、途中でウエイトしている。
        private static void PrintSeq(object obj) {
            foreach (var x in (IEnumerable)obj) {
                Console.Write(x.ToString());
                Thread.Sleep(2);
            }
        }
    }
}

いちおう、Taskクラスを使ったコードも書いてみた。これくらい単純な問題ならば、Taskクラス使うのも悪くない。

using System;
using System.Linq;
using System.Threading;
using System.Collections;
using System.Threading.Tasks;

namespace Doukaku.Org {
    class Program {
        static void Main(string[] args) {
            ParallelTest();
            Console.ReadLine();
        }

        static void ParallelTest() {
            var task1 = Task.Factory.StartNew(PrintSeq, "ABCDEFGHIJKLMNOPQRSTUVWXY");
            var task2 = Task.Factory.StartNew(PrintSeq, "abcdefghijklmnopqrstuvwxyz");
            task1.Wait();
            task2.Wait();
        }

        // 引き数 obj は、IEnumerableを実装している必要がある。引き数の値を順に出力する。
        // PrintSeqが直ぐに終わらないように、途中でウエイトしている。
        private static void PrintSeq(object obj) {
            foreach (var x in (IEnumerable)obj) {
                Console.Write(x.ToString());
                Thread.Sleep(2);
            }
        }
   }
}
  

Posted by gushwell at 23:00Comments(0)TrackBack(0)

2014年08月20日

C#で出力の一時停止と再開

   このエントリーをはてなブックマークに追加 Clip to Evernote
どう書く?orgに感謝を込めて」シリーズ その34

今回の投稿が、1111回目となります \(^o^)/

■問題
起動すると、標準出力に1秒毎に'a'の1文字を出力し続けるプログラムで、 以下の条件を満たすものを「どう書く?」
'q'キーが押されるとプログラムは終了する
出力中に'p'キーが押されると一時停止する
一時停止中に'p'キーが押されると出力を再開する 
         ※ すみなせん、出題者不明です。

3つの方法(Timer, Thread, Task)で書いています。
BackgroundWorkerで使うって方法もあるけど、それほど代わり映えしないので書いてません。
まずは、Timerを使ったコード
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Doukaku.Org {
    class Program {
        static void Main(string[] args) {
            Execute();
            Console.WriteLine("Fin");
        }

        // System.Timers.Timerを使ったコード
        private static void Execute() {
            using (var timer = new System.Timers.Timer()) {
                timer.Interval = 1000;
                timer.Elapsed += (s, e) => Console.Write('a');
                timer.Enabled = true;
                while (true) {
                    var key = Console.ReadKey(false);
                    if (key.KeyChar == 'q')
                        break;
                    if (key.KeyChar == 'p') {
                        timer.Enabled = !timer.Enabled;
                    }
                }
                timer.Enabled = false;
            }
        }
    }
}
次はThreadを使ったコード
private static void Execute() {
    ManualResetEvent mre = new ManualResetEvent(true);
    Thread thread = new Thread(
        () => {
            while (true) {
                Console.Write('a');
                Thread.Sleep(1000);
                mre.WaitOne();
            }
        }
    );
    thread.Start();
    bool suspend = false;
    while (true) {
        var key = Console.ReadKey(false);
        if (key.KeyChar == 'q') {
            mre.Set();
            break;
        }
        if (key.KeyChar == 'p') {
            if (suspend)
                mre.Set();
            else
                mre.Reset();
            suspend = !suspend;
        }
    }
    thread.Abort();
}

最後は、Taskを使った例。
なんだか、だんだん複雑になっていくなー。 前のコードと同じようにManualResetEventを使って、一時停止を実現しているけど、他によい方法はないのかしら?
Taskクラスは複雑すぎて僕には使いこなせません。Threadのほうがはるかに単純で扱いやすいと思うのは僕だけでしょうか?

private static void Execute() {
    CancellationTokenSource tokenSource = new CancellationTokenSource();
    ManualResetEvent mre = new ManualResetEvent(true);   
    var task = new Task(
        () => {
            while (!tokenSource.IsCancellationRequested) {
                Console.Write('a');
                Thread.Sleep(1000);
                mre.WaitOne();
            }
        },
        tokenSource.Token
    );
    try {
        task.Start();
        bool suspend = false;
        while (true) {
            var key = Console.ReadKey(false);
            if (key.KeyChar == 'q') {
                mre.Set();
                break;
            }
            if (key.KeyChar == 'p') {
                if (suspend)
                    mre.Set();
                else
                    mre.Reset();
                suspend = !suspend;
            }
        }
        tokenSource.Cancel();
        task.Wait();
    } finally {
        task.Dispose();
    }
}
  
Posted by gushwell at 21:54Comments(0)TrackBack(0)

2014年06月15日

C#でワーカスレッドを安全に終了させるまで待機

   このエントリーをはてなブックマークに追加 Clip to Evernote
どう書く?orgに感謝を込めて」シリーズ その16

■問題 (出題者:todogzm さん)
スレッドプールに複数のワーカスレッドが待機しており、メインスレッドはいつでもワーカスレッドに仕事を渡せるような状態になっているとします。
さて、メインスレッドからスレッドプールにいくつか仕事を与え、メインスレッドは与えた仕事すべてが終了するまで待機し、次の処理に行ってはいけない、というようなコードを書いてください。
#現実に書く機会が多そうなコードですね…。
ここでの仕事の内容は、適当に5秒から15秒の間スレッドをスリープする、というもので結構です。 また、ワーカスレッドのスレッドプール自体の使用を終了するか、または残して再利用するかは問いません。できればコメントにスレッドプールを残したかどうかを書いてください。

汎用的なクラスMyThreadPoolクラスを作成しています。
戻り値を得たいときは、MyThreadPoolクラスをちょっと改造すればOK!
使い方は、Mainメソッド見てもらえれば分かると思います。

MyThreadPoolのようなクラスを作れば、仕事が終わったかどうかは、MyThreadPoolクラス側がやってくれるので、仕事をする MyWork メソッドは、仕事そのものに専念できます。
.NET 4.5 の今ならばTaskクラス使ったほうがいいかなと思いますが...

■C#で書いたコード
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;

namespace Doukaku.Org {
    class Program {

        static void Main(string[] args) {
            MyThreadPool tp = new MyThreadPool();
            tp.Enqueue(MyWork,7);
            tp.Enqueue(MyWork,12);
            tp.Enqueue(MyWork,15);
            tp.Enqueue(MyWork,2);
            tp.WaitAll();
            Console.WriteLine("Completed");
            Console.ReadLine();
        }

        static void MyWork(object state) {
            var id = System.Threading.Thread.CurrentThread.ManagedThreadId;
            Console.WriteLine("Stread Start {0} arg={1}",id, state);
            Thread.Sleep((int)state * 1000);
            Console.WriteLine("Stread End {0}", id);
        }
    }

    public class MyThreadPool {
        List<WorkItemInfo> list = new List<WorkItemInfo>();

        public void Enqueue(WaitCallback callback, params object[] args) {
            var workItemInfo = new WorkItemInfo {
                Args = args,
                MethodInfo = callback.Method,
                AutoResetEvent = new AutoResetEvent(false),
            };
            list.Add(workItemInfo);
            WaitCallback wc = new WaitCallback(this.Call);
            ThreadPool.QueueUserWorkItem(wc, workItemInfo);           
        }

        private void Call(object state) {
            WorkItemInfo info = (WorkItemInfo)state;
            info.MethodInfo.Invoke(null, info.Args);
            info.AutoResetEvent.Set();
        }

        public void WaitAll() {
            foreach (var info in list) {
               info.AutoResetEvent.WaitOne();
            }
        }

        private class WorkItemInfo {
            public MethodInfo MethodInfo { get; set; }
            public object[] Args { get; set; }
            public AutoResetEvent AutoResetEvent { get; set; }
        }
    }
}
  
Posted by gushwell at 21:30Comments(0)TrackBack(0)

2009年11月12日

Thread.Suspend ,Thread.ResumeはObsolete

   このエントリーをはてなブックマークに追加 Clip to Evernote
いまさらですが、
Thread.Suspend ,Thread.Resume メソッド って、 Obsolete属性が付いているので、使わないほうが良いんですね。

MSDN ライブラリにも「Suspend メソッドと Resume メソッドを使用して複数のスレッドの動作を同期することは避けてください。」って書かれていました。
  
Posted by gushwell at 21:34Comments(0)TrackBack(0)