質問をすることでしか得られない、回答やアドバイスがある。

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

新規登録して質問してみよう
ただいま回答率
85.48%
排他制御

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

C#

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

MVC

MVC(Model View Controller)は、オブジェクト指向プログラミングにおけるモデル・ビュー・コントローラーの総称であり、ソフトフェア開発で使われている構築パターンとしても呼ばれます。

アーキテクチャ

アーキテクチャとは、情報システム(ハードウェア、OS、アプリケーション、ネットワーク等)の設計方法、設計思想、設計思想に基づいて構築されたシステム構造をアーキテクチャと呼びます

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Q&A

1回答

1524閲覧

UseCaseの実行を制限したい

tonica

総合スコア0

排他制御

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

C#

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

MVC

MVC(Model View Controller)は、オブジェクト指向プログラミングにおけるモデル・ビュー・コントローラーの総称であり、ソフトフェア開発で使われている構築パターンとしても呼ばれます。

アーキテクチャ

アーキテクチャとは、情報システム(ハードウェア、OS、アプリケーション、ネットワーク等)の設計方法、設計思想、設計思想に基づいて構築されたシステム構造をアーキテクチャと呼びます

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

0グッド

0クリップ

投稿2021/09/14 08:05

編集2021/09/14 12:28

質問内容

趣味で学習を兼ねてC#でデスクトップアプリを作っていて、
コードが複雑になってきたのでクリーンアーキテクチャで書き直しているのですが、

例えばRepositoryからデータを読み込んで、UseCaseに応じてDomainで処理を行うとして、
保存UseCaseや読み込みUseCaseの実行時に、他の処理(非同期を含む)を行うUseCaseの実行を制限したいのですが
その処理はどのレイヤーに書いたらいいのでしょうか。

Controllerで入力を制限する場合はUseCaseからControllerにそれを伝えることになると思いますが、
調べた限りController->UseCase->Presenterと一方方向になると思いますし、
Controllerでその制限を実装しなかった場合実行時にDomainでエラーになるので、
Applicationレイヤーで実装すべきかなと思うのですが、そのレイヤーで状態を持っていいのか等、
色々ネットでクリーンアーキテクチャについて調べたのですが混乱しています。

通常UIスレッドで同期的に行えば上記問題は発生しませんが、UseCaseにawaitを含むコードがあるので
処理->UI更新までのUseCase単位での排他制御(実行の制限)をする必要があるといった感じです。

こういう問題は重い処理を行うアプリでは頻出すると思うので、同期処理と非同期な重い処理を分けて、
重い処理(タスク)を管理する仕組みがあるのではないかと思うのですが、
そういった仕組みを作って解決するべきでしょうか。

####### 追記
コードの例です。WPFなのでViewModelでBindingを行っていますが、質問の部分はInteractorの各UseCaseです。
例のためTask.Delayを使っていますが、ProcUseCaseを呼び出した直後にLoadUseCaseを呼び出すとエラーになります。
各UseCaseを排他的に呼び出す処理をInteractorに持たせるか、もしくは非同期な処理を管理するクラスを作るか、
このあたりの設計が分からず悩んでいます。
書いていて思ったのですが後者のほうが簡単かもしれません…

C#

1using System; 2using System.Threading.Tasks; 3using System.Windows; 4 5namespace WpfArchTestApp 6{ 7 internal class Entity 8 { 9 public string Data 10 { 11 get { return disposed ? throw new ObjectDisposedException("Entity") : data; } 12 set { data = value; } 13 } 14 private string data; 15 private bool disposed = false; 16 public void Dispose() { disposed = true; } 17 } 18 19 internal class Repository 20 { 21 public async Task<Entity> Load() 22 { 23 await Task.Delay(1000); 24 return new Entity { Data = "TestStringData" }; 25 } 26 27 public async Task Save(Entity _) 28 { 29 await Task.Delay(1000); 30 } 31 } 32 33 internal class DomainService 34 { 35 public static async Task HeavyWork(Entity entity) 36 { 37 for (int i = 0; i < entity.Data.Length; ++i) 38 { 39 for (int j = 0; j < entity.Data.Length; ++j) 40 { 41 if (entity.Data[i] >= entity.Data[j]) continue; 42 char[] charArray = entity.Data.ToCharArray(); 43 charArray[j] = entity.Data[i]; 44 charArray[i] = entity.Data[j]; 45 entity.Data = new string(charArray); 46 await Task.Delay(20); 47 } 48 } 49 } 50 } 51 52 internal class Interactor 53 { 54 public event EventHandler DataUpdated; 55 public string Data { get { return entity == null ? "" : entity.Data; } } 56 57 private readonly Repository repository; 58 private Entity entity; 59 60 public Interactor(Repository repository) 61 { 62 this.repository = repository; 63 } 64 65 public async void LoadUseCase() 66 { 67 if (entity != null) 68 { 69 entity.Dispose(); 70 entity = null; 71 } 72 entity = await repository.Load(); 73 DataUpdated?.Invoke(this, EventArgs.Empty); 74 } 75 76 public async void ProcUseCase() 77 { 78 if (entity == null) return; 79 await DomainService.HeavyWork(entity); // 他のUseCaseが呼ばれるとエラー 80 DataUpdated?.Invoke(this, EventArgs.Empty); 81 } 82 83 public async void SaveUseCase() 84 { 85 if (entity == null) return; 86 await repository.Save(entity); 87 entity.Dispose(); 88 entity = null; 89 DataUpdated?.Invoke(this, EventArgs.Empty); 90 } 91 } 92 93 internal class MainWindowViewModel : VMCommon.ViewModelBase 94 { 95 public VMCommon.DelegateCommand LoadCommand { get; } 96 public VMCommon.DelegateCommand ProcCommand { get; } 97 public VMCommon.DelegateCommand SaveCommand { get; } 98 public string Data 99 { 100 get { return data; } 101 private set 102 { 103 if (data == value) return; 104 data = value; 105 RaiseProeprtyChanged(); 106 } 107 } 108 109 private readonly Interactor interactor; 110 private string data; 111 112 public MainWindowViewModel() 113 { 114 Repository repository = new Repository(); 115 interactor = new Interactor(repository); 116 LoadCommand = new VMCommon.DelegateCommand(_ => interactor.LoadUseCase()); 117 ProcCommand = new VMCommon.DelegateCommand(_ => interactor.ProcUseCase()); 118 SaveCommand = new VMCommon.DelegateCommand(_ => interactor.SaveUseCase()); 119 interactor.DataUpdated += Interactor_DataUpdated; 120 } 121 122 private void Interactor_DataUpdated(object sender, EventArgs e) 123 { 124 Data = interactor.Data; 125 } 126 } 127 128 public partial class MainWindow : Window 129 { 130 public MainWindow() 131 { 132 InitializeComponent(); 133 DataContext = new MainWindowViewModel(); 134 } 135 } 136}

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

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

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

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

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

dodox86

2021/09/14 09:48

※これは現在のご質問内容に対する非難ではありません。 私の個人的な主観も大きいですが、「クリーンアーキテクチャ」と言うもの自体がそれほどまだメジャーな印象が無いので、C#の実装にまで踏み込んで良し悪しを討議できるような活発な質疑応答は少し難しいように思います。もちろん、既に習熟、あるいは実務に適用されようとしている方からの有効な回答はあるかもしれませんが。それでも、質問者tonicaさんがどんなコードをそれぞれのレイヤーに当てはめて書こうとしているか具体例を明示すれば、多少離れたとしても有用な質問、回答になり得るかと思います。(個人的には興味ある部分です)
tonica

2021/09/14 12:24

私自身もクリーンアーキテクチャ自体をまだよく理解しておらず、ネットで調べながら見様見真似で実装していまして、私としてはアプリのコードを見やすく保守しやすく整理できればそれでいいのですが、クラス分けの指針がわからず、クリーンアーキテクチャのようなレイヤーで分けて見ようと思い〇〇Repositoryなど機能毎に現在整理しています。 GUIのアプリでは非同期処理を行う場面も多くあると思いますし、その時にUI操作によって起こる処理のうち排他的に実行(または中断)しなければいけないものがある場合に、ViewやControllerを含めどのように設計すればいいのか、というのが質問の趣旨になります。すみません、質問時に整理できていなかったかもしれません。
BluOxy

2021/09/14 16:23 編集

非同期処理は何を行うために使っているのでしょうか。データベースアクセスでしょうか。もしくは Web API やソケット通信のような何か外部と通信しているのでしょうか。もしくはただ時間がかかる計算処理を行っているのでしょうか。
tonica

2021/09/14 16:42

ただ時間がかかる処理です。ピクセル一つ一つを処理する画像処理のような感じです。同期処理にするとウィンドウごと固まるので非同期にしています。
BluOxy

2021/09/15 01:29

リポジトリで行っている async/await も同じ理由で非同期にしているのでしょうか。実装を濁しているのかわかりませんが、Save は必要でしょうか。
BluOxy

2021/09/15 02:01

リポジトリの Load で新しい Entity を生成しているだけのようなので、そもそもリポジトリは必要でしょうか。もしアプリケーション外とやり取りをしていないなら必要でないと思います。
tonica

2021/09/15 03:04 編集

説明が足りずすみません、repositoryでは実際にはファイルの読み書き・パースを行なっています。データを圧縮して保存しているため、解凍・パースに数秒かかるので非同期にしています。 質問の部分はUseCaseの部分なので、Entityに対する処理やアプリ外との入出力は例として書いています。Saveが空なのもその為です。
guest

回答1

0

保存UseCaseや読み込みUseCaseの実行時に、他の処理(非同期を含む)を行うUseCaseの実行を制限したい

ユースケースは通常そのような実行を制限するために修正しないといけないケースはないと思います。
あくまでビジネスロジックの変更があったときに限りユースケースを修正するようにしないとユースケースの責務の範疇を超えてしまいます。

今回はユースケースの実体(Interactor) が Entity を持っていることでユースケースがユースケース以上のことをしているため制限が必要になっていますが、それを変えれば良いはずです。

Entity は一般的に Repository から生成されることが多いです。Entity を作成するための元データはデータベースやWeb API経由で取得することが多いです。

つまり、永続的なデータはデータベースを管理するDBMS(例えばSQL Server)や外部のサーバーアプリケーションで持つことが一般的です。排他制御もそこで行われます。

repositoryでは実際にはファイルの読み書き・パースを行なっています。データを圧縮して保存しているため、解凍・パースに数秒かかるので非同期にしています。

ということはファイルが永続的なデータになると思います。
複数のスレッドから非同期で同時に書き込まれる場合はこの場合 Repository で排他制御を行うのが適切だと思います。
SemaphoreSlim を使って Save メソッドに対して排他制御を行いましょう。

同期処理にするとウィンドウごと固まるので非同期にしています。

時間のかかる処理だけ非同期で処理するのは問題ありませんが、同一の Entity の参照に対して複数のスレッドから同時に書き込まれる設計にしないよう気を付けてください

他に気になる点

1.Interactor が Repository の実体に依存しているのでこれ等ソースコードの実装はクリーンアーキテクチャではなくレイヤードアーキテクチャに近いと思います。

2.Entity 自体の振る舞いが Dispose メソッド1つしかないのでドメイン貧血症になっています。データベースや WebAPI など外の都合に影響されないなら DomainService をわざわざ作らずに下記のようにするのが十分な気もしますが、いかがでしょう。

C#

1// 消す 2// internal class DomainService { } 3 4internal class Entity 5{ 6 public string[] Data 7 { 8 get { return disposed ? throw new ObjectDisposedException("Entity") : data; } 9 set { data = value; } 10 } 11 private string[] data; 12 private bool disposed = false; 13 public void Dispose() { disposed = true; } 14 15 public async Task<string> HeavyWork() 16 { 17 for (int i = 0; i < Data.Length; ++i) 18 { 19 for (int j = 0; j < Data.Length; ++j) 20 { 21 if (Data[i] >= Data[j]) continue; 22 char[] charArray = Data.ToCharArray(); 23 charArray[j] = Data[i]; 24 charArray[i] = Data[j]; 25 Data = new string(charArray); 26 await Task.Delay(20); 27 } 28 } 29 } 30 31}

3.Entity が Dispose を持っているのも何だか Entity 以上のことをしているように思います。もし Image のようなリソースを持っているのであれば Entity の外(アプリケーションやフレームワーク、リポジトリなど)で生成したリソースをDIで受け取るのが適切だと思います。リソースを破棄するときも Entity の外 (生成したとき同じクラス)で行います。

C#

1internal class Entity 2{ 3 public Image Image { get; } 4 5 public Entity(Image image) 6 { 7 Image = image; 8 } 9} 10 11 12public class Program 13{ 14 public void Main() 15 { 16 var image = new Image(); 17 var entity = new Entity(image); 18 19 // TODO: 業務ロジックを書く 20 21 // 必要がなくなったら Dispose する 22 image.Dispose(); 23 } 24}

4.Presenter クラスがありませんが、DataUpdated は Interactor ではなくそちらで持つのが適切と思います。

5.規模が大きくなかったり、単体テストなどを考えていないなら今のようにインターフェースが存在しない実装で不満はないのかもしれませんが、各々のクラスが密結合になっているのでインターフェース等を使って疎結合にした方が機能変更には強くなります。

投稿2021/09/15 05:55

編集2021/09/15 06:11
BluOxy

総合スコア2663

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

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

BluOxy

2021/09/15 12:36 編集

クリーンアーキテクチャに関しては私も疎い部分がありますので、気になる点があればコメントで書いていただければ嬉しいです
tonica

2021/09/15 16:46 編集

回答ありがとうございます。 ご指摘等納得する部分が多いのですが、私自身まだ設計に関する理解が足らない部分が多く、 整理していますのでまた後日返信・解決等をさせていただきます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問