C# のいわゆる lock は、ロックがなされることを保証はしてくれるのですが、複数の待ちスレッドが発生した場合に順序性が担保されません。
どうにかして順序性を確保できないかなぁと思ったのですが...
結論から言うと、ロックだけで順序性を担保することはできないのであります。
だってロックしたらスレッドを消費してしまうから。
スレッドが消費されると未実行の Task の割り当ては生成時間順ではないから。
以下のコードは、スレッドプールに空きがあるうちは、確かに呼び出した順番に処理される可能性は高いのですが、Task.Factory.StartNew() とかでたくさんの処理が予約されたりしたならば、結果的にスレッドの割り付け自体がランダムなので、ロックを要求するタイミングはランダムになってしまうわけで、Taskそのものをキューに登録して順番に実行する以外に、確実な順序性処理はできないや、ということになりました。
通常の lock() に比べれば、タイムアウト処理とか書けるのですけれども、ある程度の順序性を守れてタイムアウト管理したいのであれば、ReaderWriterLock でいいやんけ、というオチに...
ThreadPool.SetMinThreads メソッドで、かなり大きな値にしてしまえば事実上は...いやいや確実性がないですね。
// タイムアウトの実装がまだ。タイムアウト監視スレッドを OrderingLockManager で回せばいい。 // 1000[ms]単位でじゅうぶん。 using System; using System.Collections.Generic; using System.Threading; namespace Lock { /// <summary> /// /// </summary> public class OrderingLockInstance { /// <summary> /// /// </summary> protected string Identifier { get; set; } } /// <summary> /// /// </summary> internal class OrderingLockManager { /// <summary> /// /// </summary> static protected Dictionary<OrderingLockInstance, Queue<OrderingLock>> lockQueue = new Dictionary<OrderingLockInstance, Queue<OrderingLock>>(); /// <summary> /// /// </summary> static protected OrderingLockManager instance = null; /// <summary> /// /// </summary> public static OrderingLockManager Instance { get { if(instance == null) { lock(lockQueue) { if(instance == null) { instance = new OrderingLockManager(); } } } return instance; } } /// <summary> /// /// </summary> /// <param name="locker"></param> /// <param name="lockInstance"></param> public void EnQueue(OrderingLock locker, OrderingLockInstance lockInstance) { lock (lockQueue) { if(lockQueue.ContainsKey(lockInstance) == true) { // 誰か忙しい人がいる lockQueue[lockInstance].Enqueue(locker); } else { lockQueue.Add(lockInstance, new Queue<OrderingLock>()); // 走りだせ! locker.Ready.Set(); } } } /// <summary> /// /// </summary> /// <param name="locker"></param> /// <param name="lockInstance"></param> public void Finished(OrderingLock locker, OrderingLockInstance lockInstance) { lock (lockQueue) { if (lockQueue.ContainsKey(lockInstance) == true) { if(lockQueue[lockInstance].Count == 0) { // おわり。 lockQueue.Remove(lockInstance); } else { // まだだ、まだ終わらんよ! lockQueue[lockInstance].Dequeue().Ready.Set(); } } else { // ERROR!(あってはならないルート) throw new InvalidOperationException("Method calling sequence error."); } } } /// <summary> /// /// </summary> protected OrderingLockManager() { // NOP } } /// <summary> /// /// </summary> /// <example> /// このクラスの一般的な利用方法を次の例に示します。 /// <code><![CDATA[ /// OrderingLockInstance lockInstance = new OrderingLockInstance(); /// /// ... /// /// using (new OrderingLock(lockInstance)) /// { /// // Do something /// } /// ]]></code> /// </example> public class OrderingLock : IDisposable { /// <summary> /// /// </summary> protected AutoResetEvent ready = new AutoResetEvent(false); /// <summary> /// /// </summary> protected OrderingLockInstance lockInstance = null; /// <summary> /// /// </summary> internal AutoResetEvent Ready { get { return ready; } } /// <summary> /// /// </summary> /// <param name="lockInstance"></param> public OrderingLock(OrderingLockInstance lockInstance) { this.lockInstance = lockInstance; OrderingLockManager.Instance.EnQueue(this, lockInstance); ready.WaitOne(); } /// <summary> /// /// </summary> ~OrderingLock() { // using パターンを使わない人のために // 次の人へ if (lockInstance != null) { OrderingLockManager.Instance.Finished(this, lockInstance); lockInstance = null; } } /// <summary> /// /// </summary> public void Dispose() { // きっと呼んでくれる...! // 次の人へ if (lockInstance != null) { OrderingLockManager.Instance.Finished(this, lockInstance); lockInstance = null; } } } }
コメントする