2017-12-30 54 views
0

私のコードでは説明できない動作が検出されていますが、正常に動作していても、本当に私を悩ます。 以下のスクリプトは、n_totalのメモ帳のプロセスを作成して実行しますが、毎回最大n_cpusのメモ帳しか実行できません。最初に、n_cpusプロセスが起動され、残りの1つ以上の実行中のメモ帳が終了すると、残りのプロセスが開始されます。各メモ帳プロセスは、コード内のProcess.Exitedイベントをトリガーするウィンドウを閉じるだけで、ユーザーが終了することができます。今度は、ループ内で、変数pを再利用して、新しいメモ帳が必要になるたびにプロセスProcessクラスをインスタンス化します。p.Exited += p_Exited;n_cpus = 3があるとします。それらの3つの同時メモ帳を生成します。私は、pp.Exitedがオブジェクトに属していますが、最後のインスタンスpだけがイベントを発生させることを期待しています...どのメモ帳を閉じてもイベントは発生し、新しいメモ帳が表示されます。どうしたの?作成するすべてのプロセスを記憶している代理人EventHandlerのオブジェクトレスリストがありますか?イベントProcess.Exitedは、whileループで再インスタンス化した後のオブジェクトの寿命を延ばします

using System; 
using System.Diagnostics; 
using System.Threading; 

class Program 
{ 
    // Summary: generates processes for "n_total" notepads allowing for a maximum of "n_cpus" 
    //   notepads at each time. Every time a notepad closes another appears until all are run. 
    //   A single variable "p" instantiates the class "Process" and the event "p.Exited" 
    //   updates the number of running processes "n_running". 

    static int n_running = 0; // number of notepads running each time 
    static void Main() 
    { 
     int n_cpus = 3; 
     int n_total = 3 * n_cpus; 
     int i_run = 0; 

     while (i_run < n_total) // Process generating routine until all are run 
     { 
      if (n_running < n_cpus) // Only a maximum of n_cpus running at each time 
      { 
       n_running++; 
       i_run++; 

       Process p = new Process(); // A new object per process 
       p.StartInfo.FileName = "cmd.exe"; 
       p.StartInfo.Arguments = "/c notepad.exe"; 
       p.StartInfo.UseShellExecute = false; 
       p.StartInfo.CreateNoWindow = true; 
       p.EnableRaisingEvents = true; 
       p.Exited += p_Exited; // Is this associated with a particular object "p", right? 
       p.Start(); 
      } 
      else Thread.Sleep(1000); // Waits 1s before checking for new terminated processes 
     } 
    } 

    static private void p_Exited(object sender, EventArgs e) 
    { 
     n_running--; // Updates the number of active processes. Triggers new future processes 
    } 
} 
+0

新しいプロセスを作成し、それを設定してイベントを発生させ、イベントをサブスクライブして出口に通知します。あなたがそれを求めたときにすべてが機能します。 – Jimi

+0

Cmd.exeの使用が正しくない、コンソールモードプロセスを開始するときだけブロックします。 'cmd.exe/c start/wait notepad.exe'を使う必要があります。しかし、cmd.exeを使わずにNotepad.exeを直接起動するほうが簡単です。もう一つのバグを修正してください。intはSemaphoreSlimに代わる適切なものではありません。 –

+0

@Hans興味深いことに、SemaphoreSlimクラスについては、このケースでは適切であるように見えますが、もっと詳しく読む必要があります。今度は 'cmd.exe'のために、これは実際にcmdから起動されなければならない別のexeファイルです(あるいは少なくとももっと便利な方法です)。しかし、あなたが提案する 'start/wait'の変更は、アプリケーションがコマンドラインから起動され、コマンドスクリプトから起動されない場合にのみ変化します。換言すれば、それは常にメモ帳が閉じるのを待つ。 **プログラムの実行**:[link](https://ss64.com/nt/start.html) – epp

答えて

0

私はあなたがおそらくここに障害のある仮定のカップルを作っていると思う:

変数はオブジェクトではありません、彼らはオブジェクトへの参照です。オブジェクトを変数に代入する場合(newなど)、以前にその変数で参照されていたオブジェクトを「置き換え」たり、削除または削除したりすることはありません。前のオブジェクトへの参照がもうない場合は、かもしれない いくつかの点ではが残っていますが、オブジェクトはまだ存在し、他のものが存在する可能性があります。一部のコンパイラの最適化では、コンパイラがその変数が再び使用されていないと判断した場合、そのオブジェクトを参照する変数がスコープから外れる前にガベージコレクションを取得できます。多くの開発者がイベントを登録するときに、耳を傾けされているオブジェクトの寿命は今、リスナーの寿命に展開されていることを実現するために失敗するので

イベントパターンは、おそらくC#で「メモリリーク」の唯一最大の源であります(リッスンされているオブジェクトにリスナーへの参照があるため、この参照によってガベージコレクタはオブジェクトを収集しません)。 p_Exitedは静的なので、作成したすべてのProcessオブジェクトは、イベントの登録を解除するまで、「ルート」されます(ガベージコレクションされません)。

変数pは、クラスのプロセス

をインスタンス化するために再使用されているあなたも、実際にp変数を「再利用」されていません。これはループの範囲内で宣言されているので、ループのたびに、pは実際には "まったく新しい"変数です。これは、クロージャー(C#言語チームgot this wrong)であっても特に重要な違いになります。

+0

ありがとうございます。私が正しく理解していれば、私は個々の 'Process p = new Process();'( 'p'は他のオブジェクトを参照しているため)の情報にアクセスすることはできませんが、関連付けられたプロセスが終了したときにイベントを発生させます。多数の 'n_total'プロセスの場合、強制終了したプロセスに関連するすべてのメモリを強制的に解放すべきではありませんか? 'p_Exited'の中に'(Process)sender.Close(); 'のようなものがありますか? – epp

+0

"強制的に"メモリを解放する唯一の方法は、GC.Collect()を呼び出してガベージコレクタを強制的に実行させ、それを行う必要がある状況はほとんどありません。あなたがすべきことは、イベントハンドラを登録解除することによってオブジェクトへの参照が残っていないことを確認することです: '((Process)sender).Exited - = p_Exited' –

関連する問題