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

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

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

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

C#

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

プログラミング言語

プログラミング言語はパソコン上で実行することができるソースコードを記述する為に扱う言語の総称です。

非同期処理

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

Q&A

解決済

2回答

1388閲覧

異なるexe間で実行ファイルを排他的制御したい

Satou

総合スコア1

排他制御

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

C#

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

プログラミング言語

プログラミング言語はパソコン上で実行することができるソースコードを記述する為に扱う言語の総称です。

非同期処理

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

0グッド

2クリップ

投稿2024/05/27 13:31

編集2024/05/28 13:32

実現したいこと

ボタン作成し、それをクリックして、実行ファイル起動する。
この実行ファイルの起動の上限数は1つまでとします。

再度ボタンが押されると先に出ていたものが閉じて終了する。
終了後に、実行ファイルの起動するような処理を行いたいです。

発生している問題・分からないこと

・1回目押下で、起動します。2回目の押下でも起動し、三回目から2回目に起動したものが削除されます。
2回目で削除されるようにしたいのですがうまくいきません。
※現状は削除ではなく、新たな起動しないようにし、メッセージを出力しています。

・異なるexe間でボタンを押しても、それぞれで上記の同じ動作をしています。
異なるexe間での起動と削除のプロセスを一つにまとめたいです。

・Processの探し方についてもよい方法がわからないです。
これについては、方法や探すもとになる値を列挙いただけるとありがたいです。

該当のソースコード

C#

1//以下のボタン押下後の共通コードです。 2 public class MutexHelper 3 { 4 private static Mutex mutex = new Mutex(false, "Global\\MyUniqueHelpFileMutex"); 5 private static bool isHelpFileRunning = false; 6 7 public static async Task OpenHelpFileAsync() 8 { 9 await Task.Run(() => OpenHelpFile()); 10 } 11 12 private static void OpenHelpFile() 13 { 14 bool hasHandle = false; 15 Process process = null; 16 try 17 { 18 try 19 { 20 hasHandle = mutex.WaitOne(TimeSpan.Zero, false); 21 } 22 catch (AbandonedMutexException) 23 { 24 hasHandle = true; 25 } 26 27 if (!hasHandle) 28 { 29 Console.WriteLine("ヘルプファイルはすでに開かれています。"); 30 return; 31 } 32 33 // "notepad.exe" が既に起動しているかどうかをチェック 34 if (!isHelpFileRunning) 35 { 36 isHelpFileRunning = true; 37 process = Process.Start("notepad.exe"); 38 } 39 else 40 { 41 Console.WriteLine("ヘルプファイルはすでに開かれています。"); 42 } 43 } 44 finally 45 { 46 if (hasHandle) 47 { 48 mutex.ReleaseMutex(); 49 } 50 } 51 52 // プロセスの終了を待機 53 if (process != null) 54 { 55 process.WaitForExit(); 56 isHelpFileRunning = false; 57 } 58 } 59 60 } 61 62//フォーム側では以下のように呼び出しています。 63private async void button1_Click(object sender, EventArgs e) 64{ 65 66 await MutexHelper.OpenHelpFileAsync(); 67}

試したこと・調べたこと

  • teratailやGoogle等で検索した
  • ソースコードを自分なりに変更した
  • 知人に聞いた
  • その他
上記の詳細・結果

実現にはMutexと非同期処理が現実的だとことでそれらを使って進めてます。
現状のコードはChartGPTを大本に細かい部分(分岐方法など)を自分なりに変更しました。

補足

ご指摘いただけるとありがたいです。

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

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

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

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

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

YAmaGNZ

2024/05/27 22:15

検索されたとのことですが、検索した結果参考になる情報は得られなかったということでしょうか。 もしそうなのであれば、どのような文言で検索されたかを参考までに教えていただけないでしょうか。 >Processの探し方についてもよい方法がわからないです こちらに関しては「C# プロセス 列挙」なり「C# プロセス 検索」とでも検索すればいろいろ情報が出てきそうな気がします。
YAmaGNZ

2024/05/28 02:37

Mutexにて排他処理を行いたいとのことですが、プログラムを起動~終了までMutexを取得して排他するということを想定していると思うのですが、現状はtry~finallyにてプロセスを起動後すぐにMutexをリリースしているのであまり意味はないのではないでしょうか?
kikukiku

2024/05/28 05:10

>ボタン作成し、それをクリックして、実行ファイル起動する。 ボタンから起動される実行ファイルというものは、 ソースコード上はnotepad.exeとなっていますが、 自身で変更可能な実行ファイル(新しい機能を追加開発可能か?)を想定していますか?
kikukiku

2024/05/28 05:21

もし、ボタンから起動される実行ファイルが、notepad.exeのように 変更不可能な実行ファイルだった場合、要望の機能を実装するためには、 notepad.exeを別プロセスから終了する機能が必要になります。 この場合具体的な方法は、プロセスをkillする手法になると思いますが これは強制終了させるものであるため、おすすめしません。 全体の設計から見直した方が良いと思います。
winterboum

2024/05/28 11:10

A:「実現したいこと」 の記述 と B:「発生している問題・分からないこと」の記述に不整合があるように見えます。どうしたいのかがわからないです。A,B の内容を修正してください A では 「再度ボタンが押されると先に出ていたものが閉じて終了する。」とあります。 B では 「2回目でブロックされるようにしたい」とあります。 つまり A では 動いているものが有ったらそれを終了させ(で 新たに起動させ)る と読めます B では 動いているものが有ったら 新たに起動するのは止める と読めます。 どちらでしょう
Satou

2024/05/28 12:16

レス遅くてすみません。 YAmaGNZ> まず検索文言についてはプロセスについては「C# プロセス プロパティ 関数 終了処理 」非同期については「C# 非同期 Task スレッド 終了判定」、排他制御については「C# Mutex グローバルMutex」以上を主に検索ワードにしていました。 プロセスについては、GetProcessesByName()やGetProcesses()などで名称やid、ハンドルなどで探す方法に行きつきましたが、それ以外の方法も検証してみたかったのでお聞きしたかったです。 またMutexを明示的にリリースしてるのは、明示してリリースしなければ、[AbandonedMutexException]が発生してしまうため、例外の分も余計に作らなければならないので可能ならば同じタスク内でリリースしようとしてます。 kikukiku> 実行ファイルは、変更不可な実行ファイルを現状はnotepad.exeにしてますがここにはあとでまた別の実行ファイルを入れる予定です。 なので現状はプロセスをKillして、終了に反応してMutexを解放し、そのあと再度新たな実行ファイルを起動するような流れを想定しています。 winterboum> 現状はAの考えで進めています。 現状としてはプロセス破棄ではなく、「動いているものが有ったら止める」ような寄りにしていますが、リターン部分は後に、 「動いているものが有ったら終了させる」処理に変換する予定です。 現状は、非同期処理とMutex部分の実装を主な課題にしていたので、紛らわしい文章になってしまいました。
winterboum

2024/05/28 12:48

では「発生している問題・分からないこと」のところの記述を勘違いが起きないように修正しておくことをお薦めします
YAmaGNZ

2024/05/28 13:27 編集

関数に入る~プロセスを起動するまでの間Mutexを取得する意味ありますか? 処理の流れは 1.Mutexを取得 2.取得できればプロセス起動 3.取得できなければプロセスを検索してKill 3-1.の後にMutex開放されるまで待つ 3-2.Mutex取得、プロセス起動 4.プロセス終了待ち 5.プロセス終了したらMutexを開放 といった流れでしょう。 また起動するプロセスがこの処理のみからの実行で他で起動されることがないのであればMutexを使わなくても 1.プロセスを検索 2.存在すればKill 3.プロセス起動 という流れだけでいいのではないでしょうか。
Satou

2024/05/28 13:42

winterboum> 修正しました。 御指摘ありがとうございます。 YAmaGNZ> 確かにいわれてみるとMutexにこだわる理由はありませんね。 exe間で共通で一つだけ起動したかったので、ずっと排他制御を念頭に置いていました。 再度修正してみます。
kikukiku

2024/05/28 23:44

YAmaGNZさんが言われているように、下記で良いと思います。 1.プロセスを検索 2.存在すればKill 3.プロセス起動
winterboum

2024/05/28 23:53

「動いているものが有ったらそれを**終了**させて 新たに起動させる」 という仕様で良いのかな。 後から来た人が前の人のを勝手に強制的に止めて自分のを動かす ってことですよね。 「複数のexeがそれを行う」とすると2つ問題が 自分以外のが起動したのを潰し合ってどれもこれも完了しないまま止まるということになる 他人が起動したプロセスを殺すことはできないので「それを終了させて」自体ができないことも起きるので。 そか、、OSが明記してないですが exe とか notepad ってのからすると windowsですね。ならこれは大丈夫か、、、
kikukiku

2024/05/29 00:07

プロセスを検索してKILLするのは、下記でできます。 System.Diagnostics.Process.GetProcesses() .Where(x => x.ProcessName == "notepad") .ToList() .ForEach(x => x.Kill());
bsdfan

2024/05/29 00:26

サブプロセスの起動時にPIDをどこかファイルに保存するようにして、そのファイルがあって書かれたPIDのプロセスがあったら終了させる、みたいなことをするのはどうでしょう。これなら別起動のexeからの対応もやりやすいかと。
YAmaGNZ

2024/05/29 00:40

あとは本当に前のプロセスを終了して新しいプロセスを起動する必要があるかという話になるかと思います。 ただ2重起動を防止するだけで足りるのであればそうするほうがいいかと思います。
kikukiku

2024/05/29 01:46

>サブプロセスの起動時にPIDをどこかファイルに保存するようにして、 >そのファイルがあって書かれたPIDのプロセスがあったら終了させる、 >みたいなことをするのはどうでしょう。これなら別起動のexeからの対応もやりやすいかと。 この処理を実施する目的はたぶん下記であろうと思います。 よって、質問者さんがこのような仕様にしたいのかどうかかと思います。 1.ボタンから起動した"notepad.exe"は終了対象にする。 2.ボタン以外から起動した"notepad.exe"は終了対象にしない。
kikukiku

2024/05/29 01:56

ボタン処理が下記と仮定した場合、 1.プロセスを検索 2.存在すればKill 3.プロセス起動 親アプリが2つあって、ほぼ同時にボタンが押された場合 処理がおかしくなる可能性が高いので、 プロセス間の排他制御は必要になろうかと思います。
KOZ6.0

2024/05/29 07:54 編集

親→ターゲットでなくて、親→子→ターゲットの順に起動しておくと子で排他制御すれば親はボタンを押すだけになるんじゃないでしょうか。あと勝ちな排他制御って難しそうですね。
KOZ6.0

2024/05/29 08:39

ちょっと気になったのですが、Mutex の名前に Global がついているということは、ターミナルサーバーなどの複数人がログインする環境では、他の人も排他処理の範囲に入るということでしょうか?
tmp

2024/05/29 08:44

どのような対処ができるか私も知りたかったのでクリップしていたのですが、 このまま流れだとKill前提になりそうなのでsystem.diagnostics.process.killを調べると https://learn.microsoft.com/ja-jp/dotnet/api/system.diagnostics.process.kill?view=netframework-4.8 CloseMainWindowを進めてるけど、でもこれだと終了しない時もありますし、どうしたらいいんだろか また、EdgeでCopilotに聞くと >通常の終了とは異なり、Killメソッドはプロセスに終了の機会を与えず、即座にプロセスを終了させます。 >これは、プロセスが開放すべきリソースを適切に開放できない場合があるため、注意が必要です。 定期的に再起動するから、問題ないと言った人もいますが・・・ 解放できないリソースの1つにMemoryMappedFileとか、昔みたきがしますが、覚えてない。
winterboum

2024/05/29 08:47

Satou さん 何をしたくて このような動きを望んでるのでしょう。もう少し具体的になりませんか。
can110

2024/05/29 09:03

> 異なるexe間で実行ファイルを排他的制御したい XY問題ではないですか? なせそうしたいのか、そのうえで困っていることを記載してください。
Satou

2024/05/29 14:02 編集

皆様コメントありがとうございます。 一度考えてる設計をお伝えします。 メイン画面(A)とオプション画面(B)が現在ある状態で、 この2画面にそれぞれにボタンを配置して、ボタン押下に反応してQ&A画面(C)を表示しようとしています。 排他的制御したいといった理由は、このCを重複して表示させたくなかったからです。 そこから浅い知識で「重複 制御」みたいな感じで調べてみた結果「Mutex」が上がったのでそれを使ってやってみようと思った次第です。 なので完全ローカル実行環境なので、不特定多数が一気に触るなどの仰々しいものではないです。 ボタン以外の起動は考えてないです。Q&A想定なのでC単体での起動はあまり考えていないです。 ※Mutex の名前に Globalがついている意味は特にないです。プロパティやメモリの違いがあるのかなっと思って実験的につけているだけです。 実際にやりたいことは、 1. AとBでボタンを押してCを表示させること 2. Cは一つしか表示させないこと 3. Cがすでに起動済みの時は、さきに起動しているCを終了させて、新しくCの画面を表示する といった流れを想定してます。 現状2重起動の防止として、さきに起動しているものを全面に出すことを考えましたが、 このCの使いようや表示内容なども考慮に入れて、今回は終了させて、新しくCの画面を表示するにしました。 ※Cは起動先によりUIが微妙に変わるため設計をするため bsdfan> サブプロセスの起動時にPIDをどこかファイルに保存する。 上記については特に考えていなかったので、方法の一つとして参考にさせてもらいます ありがとうございます。
KOZ6.0

2024/05/29 16:17

C を自作するなら、排他制御は C 自身にやらせて A と B は C を起動するのみですね。
winterboum

2024/05/29 22:22

>排他制御は C 自身にやらせて ああ、それが良さげ Cは動きっぱなしで ABからの指示で画面書き換えを行う。 ですね?
YAmaGNZ

2024/05/29 22:26

もしCが表示する内容を識別する方法が起動時の引数なのであれば、Microsoft.VisualBasic.ApplicationServicesを使ってCで2重起動の防止をすればいいだけに思えます。
kikukiku

2024/05/29 23:32

>C を自作するなら、排他制御は C 自身にやらせて A と B は C を起動するのみですね。 だいぶ前のコメントで、質問者さんに確認しましたが、 Cは、notepad.exeのように自作ではないアプリだそうです。 >Q&A画面(C)を表示しようとしています 質問者さんの上記コメントと矛盾しますが><
KOZ6.0

2024/05/30 01:55

>Cは、notepad.exeのように自作ではないアプリだそうです。 >>Q&A画面(C)を表示しようとしています >質問者さんの上記コメントと矛盾しますが>< 自作でないなら第三者に依頼して作ってもらうのかも。:P 作りを細かく指定できない場合は C を起動するプログラム D を作って排他制御し、A, B からは D を起動すればよいかと。
kikukiku

2024/05/30 03:55

>作りを細かく指定できない場合は C を起動するプログラム D を作って排他制御し、 >A, B からは D を起動すればよいかと。 確かにそれもありですね。
kikukiku

2024/05/30 03:57

ただ、自作アプリではない場合、終了が問題になります。 強制終了しかなくなるのでは?と思っています。 なので、全体の仕様を見直すように提案したのですが、 それは、強制終了の方向で進めるとのことでした。
kikukiku

2024/05/30 04:08

質問者さん 課題は出し切っていると思います。 あとは質問者さんの判断で進めれば良いと思います。 こういう方向性で進めると言って頂ければ、 皆さんサポートしてくれると思います。
Satou

2024/05/30 13:36

皆様コメントありがとうございます。 いつもレスが遅くて申し訳ございません。 KOZ6.0> おっしゃる通りCは第三者に作成いただいたものなので細かい指定はできない状態です。 ですので「 C を起動するプログラム D を作って排他制御し、A, B からは D を起動」を方針に作成しようと思います。 具体的にプログラム D は、 1.プロセスを検索 2.存在すればKill 3.プロセス起動 で進めていきたいと思います。 排他制御に関して、今回あと勝ちにしているためMutexを使用するよりも、シンプルにKill()をかけるほうがデッドロックなどの余計なことをなるべく起こさず、また競合してしてデータに不都合が起きる様なものでもないため今回は排他制御を使用せずに進めたいと思います。 ※タイトル完全無視しててすみません。 回答についてはKOZ6.0のを採用させていただきます。 当初私のやりたかった内容としてふさわしい回答だと思ったためです。 皆様たくさんのアドバイスとご指摘ありがとうございます。
kikukiku

2024/05/30 23:32

上記のような方針であるならば、Dの内容は下記のようにした方が良いです。 Dの基本的な作りは、KOZ6.0さんご提案の方法で実装し、 外部プロセス起動時に、プロセスIDを記憶しておいて、 D終了時に、外部プロセスをプロセスIDを指定してkillした方が良いです。
guest

回答2

0

解決しているようなので蛇足になりますが、質問コードに対し回答しておきます。

提示コードで2クリック目も起動する理由はMutexの仕様のためです。

2回目クリック後に通るMutex.WaitOne()は取得済のため、falseが返ることを期待するコードに見えますが、同一スレッドからの要求なのでtrueになります。これは公式ドキュメントにこう書かれてます。

The thread that owns a mutex can request the same mutex in repeated calls to WaitOne without blocking its execution. However, the thread must call the ReleaseMutex method the same number of times to release ownership of the mutex.
(意訳)Mutexを所有するスレッドは、繰り返しWaitOne() を呼び出して同じMutexを要求できる。ただし、Mutex所有権を解放するには、ReleaseMutex() をWaitOne()した回数だけ呼び出す必要がある。

そのため、自プログラムからの(複数クリックによる)多重起動抑制は別スレッドでチェックするか、Mutexとは別の方法で管理する必要があるということになります。

提示コードをすこしだけ手直しした案を書いておきます。

  • 複数回クリックにはフラグ、他プロセスからはMutexで抑制
  • UIをブロックしないようにプロセス待機をUIとは別スレッドで実行し、終了確認したらMutexを作ったUIスレッドに戻ってMutex開放します。

c#

1public class MutexHelper 2{ 3 static readonly Mutex _mutex = new Mutex(false, "MyUniqueHelpFileMutex"); 4 static bool _isHelpFileRunning = false; 5 6 public static void OpenHelpFile() 7 { 8 //このプログラムからの多重起動抑制は先にフラグで判定。 9 if (_isHelpFileRunning) 10 return; 11 12 //別プロセスからの多重起動抑制にMutexを使う 13 bool hasHandle = false; 14 try 15 { 16 hasHandle = _mutex.WaitOne(TimeSpan.Zero, false); 17 } 18 catch (AbandonedMutexException) 19 { 20 hasHandle = true; 21 } 22 23 if (hasHandle) 24 { 25 _isHelpFileRunning = true; 26 _ = Task.Run(() => 27 { 28 //別スレッドでプロセス起動し、終了するまで待つ 29 var p = Process.Start("cmd.exe"); 30 p.WaitForExit(); 31 }).ContinueWith(_ => 32 { 33 //mutexを作ったスレッドに戻り、mutexを解放 34 _mutex?.ReleaseMutex(); 35 _isHelpFileRunning = false; 36 }, TaskScheduler.FromCurrentSynchronizationContext()); 37 } 38 } 39}

ただ、実現したいことがプロセス削除して1つ起動する、なら明快に書いたほうがよさそうです。

c#

1private void button1_Click(object sender, EventArgs e) 2{ 3 foreach (var p in Process.GetProcessesByName("Notepad")) 4 p.Kill(); 5 Process.Start("Notepad"); 6}

投稿2024/05/30 15:05

編集2024/05/31 00:24
hqf00342

総合スコア358

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

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

Satou

2024/05/31 13:21

回答ありがとうございます。 参考させていただきます。
guest

0

ベストアンサー

起動する側 : メイン画面(A)とオプション画面(B)
起動される側:画面(C)
ということなので画面(C) のサンプルを投下します。(Windows Forms)

方針としては、Mutex が獲得できるまで既存の画面(C) に終了要求を行う、という感じで良いと思います。

使用する API を定義したクラスを作成します。GUID は終了要求ウインドウメッセージの登録と通知受信用フォームのタイトルに使用します。

csharp

1using System; 2using System.Runtime.InteropServices; 3 4internal class NativeMethods 5{ 6 public const string GUID = "{BD00244D-64BE-4881-A702-2F497A7B2F90}"; 7 public static int WM_APPLICATION_EXIT = RegisterWindowMessage(GUID); // 終了要求メッセージ 8 9 [DllImport("user32.dll", CharSet = CharSet.Auto)] 10 public static extern int RegisterWindowMessage(string lpString); 11 12 [DllImport("user32.dll", CharSet = CharSet.Auto)] 13 public static extern IntPtr FindWindow(string className, string windowName); 14 15 [DllImport("user32.dll", CharSet = CharSet.Unicode)] 16 public static extern bool PostMessage(IntPtr hWnd, int Msg, 17 IntPtr wParam, IntPtr lParam); 18}

通知受信用フォームです。SetVisibleCore をオーバーライドすることにより、非表示のウインドウになります。

csharp

1using System.Windows.Forms; 2 3[System.ComponentModel.DesignerCategory("Code")] 4internal class NotifyForm : Form 5{ 6 public NotifyForm() { 7 Text = NativeMethods.GUID; 8 } 9 10 protected override void SetVisibleCore(bool value) { 11 if (!IsHandleCreated) { 12 CreateHandle(); 13 } 14 base.SetVisibleCore(false); 15 } 16 17 protected override void WndProc(ref Message m) { 18 base.WndProc(ref m); 19 // 終了要求メッセージを受信したら終了します。 20 if (m.Msg == NativeMethods.WM_APPLICATION_EXIT) { 21 Application.Exit(); 22 } 23 } 24}

Main メソッドです。

csharp

1using System; 2using System.Threading; 3using System.Windows.Forms; 4 5internal static class Program 6{ 7 [STAThread] 8 static void Main() { 9 var mutex = new Mutex(false, NativeMethods.GUID); 10 try { 11 // Mutex を獲得できるまで終了要求をポストする 12 while (!mutex.WaitOne(100)) { 13 IntPtr hwnd = NativeMethods.FindWindow(null, NativeMethods.GUID); 14 if (hwnd != IntPtr.Zero) { 15 NativeMethods.PostMessage( 16 hwnd, NativeMethods.WM_APPLICATION_EXIT, 17 IntPtr.Zero, IntPtr.Zero); 18 } 19 } 20 21 // ここから通常の処理 22 Application.EnableVisualStyles(); 23 Application.SetCompatibleTextRenderingDefault(false); 24 25 // 通知受信用フォームの作成 26 var notifyForm = new NotifyForm(); 27 notifyForm.Show(); 28 29 // メインフォーム表示 30 Application.Run(new Form1()); 31 32 } finally { 33 // 終了前に Mutex を開放 34 mutex.ReleaseMutex(); 35 } 36 } 37}

投稿2024/05/29 17:42

KOZ6.0

総合スコア2681

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

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

Satou

2024/05/30 13:37

回答ありがとうございます。 ベストアンサーに選ばせていただきました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.39%

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

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

質問する

関連した質問