Rxを用いたアプリケーションの状態管理 Part 0
前回の記事からかなり間が空いていますが…。
最近、アプリケーションの状態管理をRxベースで扱おうとするとどのような感じになるのだろう…というようなことを考えていました。
UIを持つMVVMなアプリケーション向けであれば ReactiveProperty という素晴らしいライブラリがあるのですが、自分が考えているものと用途や対象が若干異なっていたので、勉強がてら最小限の機能を持ったライブラリを自作してみました。
CodePlex Archive
(いつものごとく、ソースコードのみのリリースです…)
今後よい具体例が思いつけば、解説記事を書く…かもしれません。
マルチスレッド処理の基礎(6) - フィールドの取り扱い Part 2
前回の記事の続きです。
静的フィールドは宣言時または静的コンストラクタで初期化する
シングルトンのインスタンスのように一度だけ初期化する必要のある静的フィールドは、宣言時または静的コンストラクタで初期化します。サンプルコードの「誤ったコードの例」のように、プロパティアクセス時に排他制御を行わずに初期化済みかどうかの判定を行う場合、競合により以下の問題が発生する可能性があります。
// 正しいコードの例(宣言時に初期化) public class SafeClass1 { private static readonly SafeClass1 def = new SafeClass1(); public static SafeClass1 Default { get { return def; } } } // 正しいコードの例(静的コンストラクタで初期化) public class SafeClass2 { private static readonly SafeClass2 def; static SafeClass2() { def = new SafeClass2(); } public static SafeClass2 Default { get { return def; } } } // 誤ったコードの例 public class UnsafeClass { private static UnsafeClass def; public static UnsafeClass Default { get { if (def == null) { def = new UnsafeClass(); } return def; } } }
補足:Lazyクラス
静的フィールドを初期化する方法には、上記に挙げた方法の他に、.NET Framework 4.0以降に追加されたLazy<T>クラスを使用する方法があります。
フィールド初期化処理の実行タイミングを、型の初期化時ではなくプロパティアクセス時に明示的に遅延させたい場合、Lazy
// 正しいコードの例(Lazy<T>を使用) public class SafeClass3 { private static readonly Lazy<SafeClass3> def = new Lazy<SafeClass3>(); // 例外をキャッシュしたい場合は生成処理のデリゲートを引数に渡す。 // private static readonly Lazy<SafeClass3> def = new Lazy<SafeClass3>(() => new SafeClass3()); public static SafeClass3 Default { get { return def.Value; } } }
マルチスレッド処理の基礎(5) - フィールドの取り扱い Part 1
これから数回に分けて、フィールドを更新・参照する処理をスレッドセーフにする方法を説明します。
単一のフィールドを固定値で更新する場合はvolatile修飾子をつける
処理終了要求フラグのように、単一のフィールドをあるスレッドが固定値で更新、他のスレッドがそのフィールドを参照する場合には、フィールドにvolatile修飾子をつけます。volatile修飾子をつけない場合、コンパイル時や実行時の最適化により、プログラムが開発者の意図通り動作しない可能性があります。
なお、volatile修飾子をつけるフィールドの型は、基本的には組み込みデータ型や不変クラスなどのスレッドセーフな型のみを用いるようにします。
public class LoopAction { private readonly Action action; private volatile bool shutdownRequested; public LoopAction(Action action) { this.action = action; this.shutdownRequested = false; } public void Run() { while (!shutdownRequested) { action(); } } public void ShutdownRequest() { shutdownRequested = true; } } internal class Program { internal static void Main(string[] args) { Thread.CurrentThread.Name = "Main"; var loop = new LoopAction(() => { Thread.Sleep(3000); Console.WriteLine("{0}: Running...", Thread.CurrentThread.Name); }); var t = new Thread(loop.Run); t.Name = "Loop"; t.Start(); Thread.Sleep(9500); loop.ShutdownRequest(); Console.WriteLine("{0}: Shutdown request.", Thread.CurrentThread.Name); t.Join(); Console.WriteLine("{0}: Shutdown completed.", Thread.CurrentThread.Name); } }
単一のフィールドを参照、計算後に更新する場合はInterlockedクラスを使用する
数値のカウントや合計のように、単一のフィールドを複数のスレッドで参照、計算、更新する場合には、Interlockedクラスのメソッド経由でフィールドにアクセスします。Interlockedクラスのメソッドを使用しない場合、競合によりフィールドが予期しない値になる可能性があります。
なお、Interlockedクラスのメソッドを使用する場合には、フィールドへのvolatile修飾子は不要です。
public class SomeTask { private static long totalTicks = 0; public static TimeSpan TotalTime { get { var ticks = Interlocked.Read(ref totalTicks); return new TimeSpan(ticks); } } private readonly int id; public SomeTask(int id) { this.id = id; } public void Run() { int time = new Random(id).Next(300); var sw = new Stopwatch(); sw.Start(); Thread.Sleep(time); sw.Stop(); var elapsed = sw.Elapsed; Interlocked.Add(ref totalTicks, elapsed.Ticks); Console.WriteLine("Task[{0:00}]: {1}", id, elapsed); } } internal class Program { internal static void Main(string[] args) { var count = 10; var threads = Enumerable.Range(0, count).Select(i => { var task = new SomeTask(i); var t = new Thread(task.Run); return t; }).ToArray(); foreach (var t in threads) { t.Start(); }; foreach (var t in threads) { t.Join(); }; Console.WriteLine("Total: {0}", SomeTask.TotalTime); } }
複数のフィールドを更新・参照するする可能性がある処理はlockステートメントを使用する
複数フィールドの連続参照・更新処理や、スレッドセーフでないオブジェクトへのプロパティアクセス・メソッド呼び出し等の複雑な処理を行う場合には、lockステートメントを使用します。lockステートメントを使用しない場合、競合によりプログラムが開発者の意図通り動作しない可能性があります。
また、lockステートメントを使用する際には、以下の項目を守るようにします。その他にもいくつか注意すべき点はありますが、それについては別途解説を行う予定です。
- lockで指定するインスタンスは、自クラス内の専用のreadonly objectフィールドに保持し、アクセス修飾子はprivateとする(派生クラスとの共有が必要な場合はprotectedでもよいが、基本的には避ける)
- 静的フィールドを保護する場合は静的フィールド、インスタンスフィールドを保護する場合はインスタンスフィールドを使用する
- 同一のフィールドにアクセスするアプリケーション内のすべての処理を、同じインスタンスを指定したlockステートメントの中で行うようにする
public class SomeTaskStatistics { private readonly int count; private readonly TimeSpan totalTime; public SomeTaskStatistics(int count, TimeSpan totalTime) { this.count = count; this.totalTime = totalTime; } public int Count { get { return count; } } public TimeSpan TotalTime { get { return totalTime; } } public TimeSpan AverageTime { get { return new TimeSpan(TotalTime.Ticks / Count); } } } public class SomeTask { private static readonly object StatisticsLock = new object(); private static int count; private static TimeSpan totalTime; public static SomeTaskStatistics Statistics { get { lock (StatisticsLock) { return new SomeTaskStatistics(count, totalTime); } } } private static void Done(TimeSpan time) { lock (StatisticsLock) { count++; totalTime += time; } } private readonly int id; public SomeTask(int id) { this.id = id; } public void Run() { int time = new Random(id).Next(300); var sw = new Stopwatch(); sw.Start(); Thread.Sleep(time); sw.Stop(); var elapsed = sw.Elapsed; Done(elapsed); Console.WriteLine("Task[{0:00}]: {1}", id, elapsed); } } internal class Program { internal static void Main(string[] args) { var count = 10; var threads = Enumerable.Range(0, count).Select(i => { var task = new SomeTask(i); var t = new Thread(task.Run); return t; }).ToArray(); foreach (var t in threads) { t.Start(); }; foreach (var t in threads) { t.Join(); }; var info = SomeTask.Statistics; Console.WriteLine("Count:{0}, Total: {1}, Average:{2}", info.Count, info.TotalTime, info.AverageTime); } }
※上記程度の処理であれば、lockではなくInterlocked.CompareExchange()を用いる方法でも実現は可能です。
マルチスレッド処理の基礎(4) - クラス設計に関する留意事項 Part 2
またまた間が空いてしまいましたが、前回の記事の続きです。
スレッドとインスタンスの所有関係を明確化する - スレッドセーフでないクラスの場合
スレッドセーフでないクラスのインスタンスを保護する方法は、基本的には以下の2つの方法しかありません。
ただし、2.の方法は排他制御に誤りがないことの確認がレビューのみでしかできないため、品質を確保することが難しくなります。
そのため、通常は設計レベルでスレッドとインスタンスの所有関係を明確・固定化し、1.の方法で安全性を保証するようにします。実現方法の詳細は、別途解説予定です。
※図中の「actor」は、UML標準で用いられるアクター(人型アイコンで表記するもの)ではなく、並行処理の手法として用いられるアクタークラスを意図しています。
マルチスレッド処理の基礎(3) - クラス設計に関する留意事項 Part 1
少し間が空いてしまいましたが、これから数回に分けて、マルチスレッド処理の設計・実装における注意点を解説していきます。
まずは、クラス設計に関する留意事項を説明します。
スレッドセーフなクラスとスレッドセーフでないクラスを分離し、明確化する
スレッドセーフなクラスは作成が難しいため、アプリケーション固有の処理を行うクラスでスレッドセーフを保つような設計は、基本的には避けます。通常は標準ライブラリをそのまま利用し、標準ライブラリに無い機能が必要であれば別途ライブラリとして作成し、それを利用するかたちでスレッドセーフを実現するようにします(後述する「不変クラス」は例外)。
なお、UMLで設計ドキュメントを作成する場合は、標準的な記法ではありませんが、ステレオタイプを用いると設計者の意図が読み手に伝わりやすくなります。
補足:.NET Frameworkの標準ライブラリ
マルチスレッド処理に関連する.NET Frameworkの標準ライブラリには多くのものがあります。学ぶ際には、APIリファレンスを読む前に、以下の解説記事から読むことをお勧めします。
.NET での並列プログラミング | Microsoft Docs
値の保持を目的とするクラスは、可能な限り不変にする
フィールドがすべて変更不可能となっているクラスを、不変(Immutable)クラスと呼びます。不変クラスのインスタンスは、排他制御等の特別な処理を行わなくても、すべてのスレッドから安全にアクセスすることが可能になります。
情報の更新回数が多いものを不変クラスとした場合はオブジェクト生成コストの面で不利になる場合がありますが、更新より読み取り回数の方が多い場合には、排他制御が不要になることでパフォーマンスの向上が図れる場合があります。
以下に、変更不可能なフィールドとして扱ってよい例とサンプルコードを示します。
- readonly修飾子つきの組み込みデータ型(int, double, stringなど)
- readonly修飾子つきの不変なクラス・構造体型(DateTime, TimeSpanなど、自作のクラス・構造体でもよい)
- readonly修飾子つきの組み込みデータ型・クラス・構造体のReadOnlyCollection(※ただし、コンストラクタの引数を直接利用する場合は引数を一旦コピーしたうえでReadOnlyCollectionを生成する必要がある)
// 不変な値を表すサンプルクラス。実装を簡略化するため引数のチェック処理等は省略。 public class ImmutableValue { private readonly int id; private readonly string name; private readonly ReadOnlyCollection<byte> data; public ImmutableValue(int id, string name, IEnumerable<byte> data) { this.id = id; this.name = name; this.data = data.ToList().AsReadOnly(); } public int ID { get { return id; } } public string Name { get { return name; } } public ReadOnlyCollection<byte> Data { get { return data; } } }
補足:.NET Frameworkの不変コレクションライブラリ
現時点ではベータ版ですが、Microsoft社製の不変コレクションライブラリがNuGetから入手可能です。
NuGet Gallery | Microsoft.Bcl.Immutable 1.1.32-beta
参考記事:.NETが不変になる
補足:ドメイン駆動設計における不変なクラス
マルチスレッド処理の文脈以外のソフトウェア設計手法の文脈においても、不変なオブジェクトの概念は重要な役割を果たします。
たとえば、ドメイン駆動設計においては、「値オブジェクト」という名称で、アプリケーションの構成要素の一部を不変なクラスのかたちで取り扱うことの有用性が紹介されています。
![エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践) エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)](https://images-fe.ssl-images-amazon.com/images/I/51f7WXHJYCL._SL160_.jpg)
エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)
- 作者: エリック・エヴァンス,今関剛,和智右桂,牧野祐子
- 出版社/メーカー: 翔泳社
- 発売日: 2011/04/09
- メディア: 大型本
- 購入: 19人 クリック: 1,360回
- この商品を含むブログ (131件) を見る
Actorライブラリを公開しました
Actorライブラリを作成したので、CodePlexで公開してみました。
どのようなライブラリなのかざっくり言うと、TPL Dataflowを使うと実現が楽になるような動作から並列性に関する要件を一部削る代わりにSynchronizationContextを実装し、async/awaitやRx(IObservable)をベースとしたオブジェクト間の非同期メッセージパッシングを簡単に実装できるようサポートする…という感じのものです。
ActorといえばScalaが有名ですが、C#にはScalaやF#のようなパターンマッチ構文がないので(…)、型安全に処理を取り扱えるよう、Actorの具象クラスに固有のインタフェースを持てるようにしてあります(このあたりの取り扱いもTPL Dataflowとは異なる部分かも)。
なお、前回のエントリで紹介したStateパターン用ライブラリと同じく、現状ドキュメントやAPIリファレンスがほとんど整備できていないため、ソースコードのみのリリースです。こちらも自動単体テストは通してあるので、基本動作には問題ないとは思いますが…。
もう少し様子を見て、安定して利用できるようであればNuGetでインストールできるようにしようかなどと考えています。
今後しばらくは、滞っていたマルチスレッド処理の連載に戻る…予定です。一応。
Stateパターン用ライブラリを公開しました
最近作っていたStateパターン用のライブラリをCodePlexで公開してみました。
CodePlex Archive
現状ドキュメントやAPIリファレンスがほとんど整備できていないため、ソースコードのみのリリースです。自動単体テストは通してあるので、実用上はおそらく問題ないとは思いますが…。
今後このブログで詳細を解説する予定…も現状は未定です。