🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
排他制御

排他制御とは、特定のファイル・データへのアクセスや更新を制御することです。特にファイルやデータベースへ書き込みを行う際、データの整合性を保つため別のプログラムによる書き込みを一時的に制御することを指します。

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

.NET Framework

.NET Framework は、Microsoft Windowsのオペレーティングシステムのために開発されたソフトウェア開発環境/実行環境です。多くのプログラミング言語をサポートしています。

Q&A

解決済

1回答

6771閲覧

C#でマルチプロセスアプリの排他制御を行う場合のクラス設計

marusa

総合スコア17

排他制御

排他制御とは、特定のファイル・データへのアクセスや更新を制御することです。特にファイルやデータベースへ書き込みを行う際、データの整合性を保つため別のプログラムによる書き込みを一時的に制御することを指します。

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

.NET Framework

.NET Framework は、Microsoft Windowsのオペレーティングシステムのために開発されたソフトウェア開発環境/実行環境です。多くのプログラミング言語をサポートしています。

0グッド

0クリップ

投稿2020/12/23 23:17

編集2020/12/23 23:46

前提・実現したいこと

C#で、ファイルの読み書きを同時に行わせないよう排他制御としてMutexを取り入れたプログラムを実現したい。
一度の処理で複数回読み書きがある場合、その処理がすべて終わるまでファイルの読み書きを行えないようにしたい。
例)ファイルにデータを書き込む場合、既にあるデータは書き込まないようにデータをファイルから読み込んで確認してから書き込みをする。

その際に、下記の修正よりも簡単な手段があるのかを知りたい。
ここで、ファイルの読み書きを同時に行うというのは"1ファイルに対して複数の処理で同時に読み書きする"ではなく、
"ReadWriteClassを用いて複数のプロセスで同時に読み書きする"ことで、それを制限したいという意味です。(読み書きの対象が別ファイルでも制限したい)

下記例のReadWriteClassが複数のプロセスで利用されることを想定しています。

該当のソースコード

#####修正前のソースコード

  • ReadWriteClass.cs

C#

1using System.Collections.Generic; 2using System.Linq; 3using System.Threading; 4using System.IO; 5 6namespace MutexSample 7{ 8 class ReadWriteClass 9 { 10 private static Mutex mutex = new Mutex(false, "sample"); 11 public T GetData<T>(string path) 12 { 13 //Jsonファイルからデータを読んでTに加工する。 14 string jsonData = File.ReadAllText(path); 15 var ret = Newtonsoft.Json.JsonConvert.DeserializeObject<T>(jsonData); 16 return ret; 17 } 18 19 public void CreateData(string name) 20 { 21 var newData = new DataClass { Name = name }; 22 23 mutex.WaitOne(); 24 var currentData = GetData<List<DataClass>>(""); 25 26 if (!currentData.Any((data) => data.Name.Equals(newData.Name))) 27 { 28 SaveData(""); 29 } 30 mutex.ReleaseMutex(); 31 } 32 33 public void SaveData(string path) 34 { 35 } 36 } 37}
  • DataClass.cs

C#

1namespace MutexSample 2{ 3 public class DataClass 4 { 5 public string Name { get; set; } 6 } 7}
  • OtherClass.cs

C#

1using System.Collections.Generic; 2 3namespace MutexSample 4{ 5 class OtherClass 6 { 7 public static void main(string[] args) 8 { 9 var rw = new ReadWriteClass(); 10 rw.GetData<List<DataClass>>(""); 11 } 12 } 13}

###修正前のソースコードにおける問題点

  • OtherClassのmain関数から、ReadWriteClassのGetData関数を呼び出しているが、GetData関数はファイルから読み込む処理が入っているものの排他制御されない。

  • GetData関数のReadAllText関数前後にMutexを利用した排他制御を書いた場合、CreateData関数を呼び出すとデッドロックが起きてしまうため書けない。

####修正後のソースコード

  • ReadWriteClass.cs

C#

1using System.Collections.Generic; 2using System.Linq; 3using System.Threading; 4using System.IO; 5 6namespace MutexSample 7{ 8 class ReadWriteClass 9 { 10 private static Mutex mutex = new Mutex(false, "sample"); 11 public T GetData<T>(string path) 12 { 13 mutex.WaitOne(); 14 var ret = GetDataPrivate<T>(path); 15 mutex.ReleaseMutex(); 16 17 return ret; 18 } 19 20 private T GetDataPrivate<T>(string path) 21 { 22 string jsonData = File.ReadAllText(path); 23 var ret = Newtonsoft.Json.JsonConvert.DeserializeObject<T>(jsonData); 24 return ret; 25 } 26 27 public void CreateData(string name) 28 { 29 var newData = new DataClass { Name = name }; 30 31 mutex.WaitOne(); 32 var currentData = GetDataPrivate<List<DataClass>>(""); 33 34 if (!currentData.Any((data) => data.Name.Equals(newData.Name))) 35 { 36 SaveData(""); 37 } 38 mutex.ReleaseMutex(); 39 } 40 41 public void SaveData(string path) 42 { 43 } 44 } 45}

###修正後ソースコードの問題点

  • 動作自体は、求めていたものが実現できている。
  • 今回のGetData関数のように、クラス内外から同じ動作をする機能を使いたいときに、クラス外から呼ぶための関数とクラス内から呼ぶための関数と二つ用意する必要がある。

 - これが必要な手間であるのかどうかを知りたい(もっと簡単な方法が存在する?)

試したこと

  • C# ファイル読み書き 排他制御で調べる。

 - 1ファイルに対するロックをかける方法はあるものの、プロセス全体で読み書きを制限する手段は見つけられなかった。

  • C# マルチプロセス ファイル読み書き 排他制御で調べる

 - やはり対象が1ファイルのみの物しか見つけられなかった。(ファイル自体をロックする方法)

補足情報(FW/ツールのバージョンなど)

  • .NET Framework 4.7.2
  • Windows 10
  • Visual Studio 2015

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Zuishin

2020/12/23 23:27

Mutex をつかわずとも、読み書きできるようファイルを開くと、他のスレッドからはそのファイルは開けなくなります。開こうとすると例外が発生するので、例外処理をしてください。
marusa

2020/12/23 23:31

他のスレッドからファイルを開けなくなるのは、開いているファイルのみでその他のファイルは普通に開ける状態でしょうか。 その他のファイルについても制限をかけたいと思っております。(ファイル自体をロックするというより、他の(読み書き用クラスを利用する)スレッドでも読み書き処理をロックしたい。)
Zuishin

2020/12/23 23:36 編集

そのファイルのみです。ロックするには他に色々方法がありますが、ロックしている間にそのリソースに触ろうとすると、ロックが外れるまでそのスレッドは停止するので慎重にプログラミングしてください。 https://qiita.com/tadokoro/items/28b3623a5ec58517d431
Zuishin

2020/12/23 23:51 編集

プロセスなら名前付きセマフォまたは名前付きミューテックスがいいんじゃないでしょうか。単に使い方がわからないということですか?
marusa

2020/12/23 23:51

記事の内容とコメントを読ませていただきました。 そして質問文にマルチプロセスアプリと記載していなかったため、記載させていただきました。情報が不足していて大変申し訳ありません。 プロセス間で、同時に1プロセスでしかファイルの読み書きが出来ない、といった制限が実現したいと考えております。(一つのプロセスがファイルaを開いている場合は、他のプロセスではファイルbやファイルcなどどのファイルかを問わずファイルを開けないようにしたい。)
marusa

2020/12/23 23:59 編集

現在は名前付きミューテックスを利用しており、ファイルから読むという処理とファイルに書きこむという処理を関数として用意しているのですが、それぞれの関数でミューテックスを使ってしまうと読んでから書き込む、という一連の処理をする関数(二つの関数を順番に呼び出す)で、処理中はずっとロックしたい、というときに対応できませんでした。 なので、それぞれの関数でミューテックスをロックするのではなく一連の処理をする関数でミューテックスをロックするようにしたのですが、そのようにした場合は読む関数だけを呼ぶときにミューテックスがロックされません。 なので、ファイルから読む関数と、ファイルから読む関数を呼ぶ関数、といったように用意してファイルから読む関数を呼ぶ関数でミューテックスをロックするようにしてたのですが、これを同じような関数を作るたびに作るのがちょっと手間だなと考えておりまして、これよりも簡単な方法があるのかな?という質問です。
Zuishin

2020/12/24 00:14

処理をする直前に WaitOne して、処理が終わる際に ReleaseMutex するというのを、そのファイルを扱う際に必ず行う必要があります。 例えば Mutex を保持するクラスを作り、そこに次のようなメソッドを作り、行いたいアクション(ファイルの読み書き)を引数に呼び出すようにすると、簡単に呼び出せます。特定のファイルを扱うためのクラスを作り、その内部からこれを呼び出すようにすれば更に抽象度が上がるでしょう。 void MethodName(Action action) { mutex.WaitOne(); try { action(); } finally { mutex.ReleaseMutex(); } }
guest

回答1

0

自己解決

Zuishinさんのコメントをもとに以下のように作成しました。
呼び出し側でCallRWMethodのメソッドを呼び出すようにすれば、ReadWriteClassを変更せずロックをかけることが出来ます。
関数を呼び出した時点でその関数の処理が終わるまでロックされることに注意。

  • ReadWriteClass.cs

C#

1using System.Collections.Generic; 2using System.Linq; 3using System.Threading; 4using System.IO; 5 6namespace MutexSample 7{ 8 public class ReadWriteClass 9 { 10 public T GetData<T>(string path) 11 { 12 string jsonData = File.ReadAllText(path); 13 var ret = Newtonsoft.Json.JsonConvert.DeserializeObject<T>(jsonData); 14 15 return ret; 16 } 17 18 public void CreateData(string name) 19 { 20 var newData = new DataClass { Name = name }; 21 22 var currentData = GetData<List<DataClass>>(""); 23 24 if (!currentData.Any((data) => data.Name.Equals(newData.Name))) 25 { 26 SaveData(""); 27 } 28 } 29 30 public void SaveData(string path) 31 { 32 } 33 } 34}
  • CallRWMethod.cs

C#

1using System; 2using System.Threading; 3 4namespace MutexSample 5{ 6 public class CallRWMethod 7 { 8 public ReadWriteClass Rw { get; set; } = new ReadWriteClass(); 9 private Mutex mutex = new Mutex(false, "sample"); 10 11 public void RunAction(Action action) 12 { 13 mutex.WaitOne(); 14 try 15 { 16 action(); 17 } 18 finally 19 { 20 mutex.ReleaseMutex(); 21 } 22 } 23 24 public TResult RunFunction<T, TResult>(Func<T, TResult> func, T arg) 25 { 26 mutex.WaitOne(); 27 try 28 { 29 var result = func(arg); 30 return result; 31 } 32 finally 33 { 34 mutex.ReleaseMutex(); 35 } 36 } 37 } 38}
  • OtherClass.cs

C#

1namespace MutexSample 2{ 3 class OtherClass 4 { 5 public static void main(string[] args) 6 { 7 var rw = new CallRWMethod(); 8 rw.RunFunction(rw.Rw.GetData<DataClass>, ""); 9 } 10 } 11}

追記
返り値がある場合ミューテックスがリリースされてない状態だったので修正。

追追記
returnがあってもミューテックスはリリースされるのでもう一度修正。

投稿2020/12/24 00:47

編集2020/12/24 02:20
marusa

総合スコア17

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問