none
ループ内でのSystem.Diagnostics.Process.Startについて RRS feed

  • 質問

  • 基本的なことかもしれませんが質問したいことがございます。
    ループ内でSystem.Diagnostics.Process.Startで他のプログラムを実行しています。
    /******************************************************************************************/
    呼び出す側
            static void Main(string[] args)
            {

                System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo();
                psi.FileName = "testConsole.exe";
                psi.CreateNoWindow = true;
                psi.ErrorDialog = false;
                psi.UseShellExecute = false;
                psi.RedirectStandardError = true;
                psi.RedirectStandardOutput = false;
                psi.RedirectStandardInput = false;

                for (long i = 0; i < 999999; i++)
                {
                    using (Process p = Process.Start(psi))
                    {
                        p.WaitForExit();
                        p.Close();
                        p.Dispose();
                    }
                    Console.SetCursorPosition(0, Console.CursorTop);
                    Console.Write(i);
                }
            }

    呼び出される側(testConsole.exe")
    namespace testConsole
    {
        class Program
        {
            static void Main(string[] args)
            {
                Environment.Exit(0);
            }
        }
    }

    /******************************************************************************************/
    上記を実行するとタスクマネージャーのメモリの表示が増えていき途中(20万回目のループぐらい)で空きメモリがなくなり「メモリ不足のためこの操作を実行できません」
    というエラーが出て最後まで実行されません。
    また呼び出す側が終了してもメモリの使用量が減りません。
    呼び出される側は終了すればメモリが解放されると思っていたのですが、
    これは仕方のないことなのでしょうか。
    メモリの使用量が累積しない方法が何かありますでしょうか。

    2015年3月27日 5:15

回答

  • C# のことは全く分かりませんけど。。。もしかしたら、問題の現象は環境に依存しているのかも。

    プロセスが終了すると、終了したプロセスに関連するリソースが全て解放されたタイミングで、そのプロセスが使用していたメモリ リソースが解放されます。
    (ちょっとへんな表現化もしれませんが。)
    たとえば、プロセス A が CreateProcessAsUser() API を使用してプロセス B を生成したとします。
    (さらにプロセス A が CreateRemoteThread() API をコールして、プロセス B にスレッドを作成したとします。)
    このあとプロセス B が (自発的に) に終了しても、もしプロセス A がプロセス B 生成時に取得したプロセス B のハンドルをクローズしていないと、プロセス B はそのハンドルが解放されるまで、終了した (ゾンビ) プロセスとしてメモリ上に残り続けることになります。
    (同じ理由で、他のプロセスがスレッド ハンドルをオープンしている場合も、同様の現象が起こります。)
    つまり、他のプロセスが終了したプロセスのリソースを握り続けている場合に、ご質問されているような状況が起こります。

    で、上記の現象がなぜ環境に依存して発生するかというと。。。
    セキュリティ ソフトの多くは、独自のサービスおよびドライバを組み込むことによって、その環境でのプロセスの生成/終了を監視しています。
    つまりセキュリティ ソフトが、監視対象プロセスのリソース ハンドルを適切なタイミングで解放しないと、上記で説明した状況が起こりうるわけです。
    なお、セキュリティ ソフトが組み込んでいるドライバの多くは、そのセキュリティ ソフトが組み込んだサービスを停止させるだけでは止まりません。
    ですので、セキュリティ ソフトの影響を検証するのであれば、アンインストールした状態で確認されることをお勧めします。

    蛇足ですが。。。
    上記で説明した状況が発生しているのかは、問題現象が発生したタイミングで完全メモリ ダンプを採取し、それを解析すれば判断できます。

    以上、参考になりましたら幸いに存じます。

    • 回答としてマーク 星 睦美 2015年4月1日 1:36
    2015年3月27日 9:21
  • ProcessクラスのインスタンスはGCで回収されると思いますので、増え続けることはないように思います。実際、20万回まで試してみましたが、私の環境では増え続けることはなく、増減を繰り返しながら、ほぼ一定使用量を保ったままでした(タスクマネージャーで確認)。また、VS2013の「パフォーマンスと分析」でも確認してみましたが、約10.5MBを最後までキープしました。
    試した環境は、Windows 8.1 pro 64bit メモリー16GB, VS2013 ultimate です。

    あまりこの辺りは詳しくないのですが、OS等、お使いの環境に依存するのかもしれません。


    ★良い回答には回答済みマークを付けよう! MVP - .NET  http://d.hatena.ne.jp/trapemiya/

    • 回答としてマーク 星 睦美 2015年4月1日 1:37
    2015年3月27日 8:25
    モデレータ
  • プロセスを監視するようなセキュリティソフトなどは動かしてませんか?
    • 編集済み hihijiji 2015年3月27日 8:29
    • 回答の候補に設定 星 睦美 2015年4月1日 1:39
    • 回答としてマーク 星 睦美 2015年4月1日 1:41
    2015年3月27日 8:29
  • Hoshinaです
    こんにちは

    20万件超で動作しています。

    OS:Windows 7 64ビット
    メモリ:6G
    .NET:4.5.1
    Visual Studio:2013 Community

    2つのプロジェクトをRelease版でコンパイルして、実行はVisual Studio上から行いました。
    20万回ですと、1時間近くかかります。

    今、23万回弱

    それでは

    • 回答としてマーク 星 睦美 2015年4月1日 1:38
    2015年3月27日 8:30

すべての返信

  • OSにとってプロセスの生成も管理も、とっても重い処理です。
    極力プロセスを生成しないアプリを作るのが、ソフト屋さんが最低限守るべきマナーだと思います。

    マルチプロセスで動くアプリもあるには有りますが、外的要因(プラグインやGPUを使った描画処理など)によるフリーズが避けられないなどの理由により仕方なく行ってます。

    他のプロセスとのデータのやり取りは、プロセス間で通信するかDBMSなど第3者を介して行います。
    2015年3月27日 6:46
  • hihijiji様

    ご回答ありがとうございます。

    確かに極力プロセスを生成しないよう心がけるべきだと思います。

    ただプロセスが残っていないのにメモリの使用量が減らないのはどうしてでしょうか。

    質問のカテゴリが違うかもしれませんがをご存知でしたらご教授ください。

    2015年3月27日 7:35
  • タスクマネージャのメモリの表示とは、具体的に何を指していますか?

    あと、.NET Frameworkのバージョンはわかるでしょうか?

    ※ついでに、OS環境とか、x64かどうかとか、わかる範囲で情報があれば


    • 編集済み なちゃ 2015年3月27日 7:49
    • 回答の候補に設定 星 睦美 2015年4月1日 1:42
    2015年3月27日 7:41
  • 試しに挙げられているコードを実行してみました。1万回程度の実行ですが使用メモリは16MB程度で、これでは20万回でもメモリ不足とはならなさそうな印象です。

    環境に何かヒントがあるかもしれません。

    • 回答の候補に設定 星 睦美 2015年4月1日 1:39
    2015年3月27日 7:49
  • プロセスの破棄も裏でOSが一生懸命仕事をして行います。
    数が少なければ一瞬にみえても、連続で行ったらリソースの回収が間に合わないことは十分に考えられます。
    Windowsが動く上で扱うプロセス数は通常3桁、多くても4桁でしょう。
    文字通り桁違いです。

    ちなみに16Bit Windows時代は累積起動プロセスが2桁でも不安定になってました。

    もうやめて、Windowsの空きリソースはゼロよ!
    • 編集済み hihijiji 2015年3月27日 8:21 ごめんなさい
    2015年3月27日 7:56
  • なちゃ様

    ご回答ありがとうございます。

    .NET Framework 4.5.1

    os Windows7 64bit

    タスクバーのメモリの表示はwindowsタスクマネージャーのパフォーマンスのメモリの項目のことです。

    実は今回のことはサーバープログラムと通信するdllがクライアントにあり、それがスレッドに対応していないようなので安易に考えdllを実行する処理を呼び出される側に書いて呼び出す側でParallel.For内でProcess.Startしたところ遭遇した現象です。正常に動作していたんですが20万件ほどで空きメモリがなくなりエラーが発生しました。なので最初の投稿のように単純にして実行したところ同じく20万件ほどで空きメモリがなくなりエラーが発生しました。

    2015年3月27日 8:21
  • ProcessクラスのインスタンスはGCで回収されると思いますので、増え続けることはないように思います。実際、20万回まで試してみましたが、私の環境では増え続けることはなく、増減を繰り返しながら、ほぼ一定使用量を保ったままでした(タスクマネージャーで確認)。また、VS2013の「パフォーマンスと分析」でも確認してみましたが、約10.5MBを最後までキープしました。
    試した環境は、Windows 8.1 pro 64bit メモリー16GB, VS2013 ultimate です。

    あまりこの辺りは詳しくないのですが、OS等、お使いの環境に依存するのかもしれません。


    ★良い回答には回答済みマークを付けよう! MVP - .NET  http://d.hatena.ne.jp/trapemiya/

    • 回答としてマーク 星 睦美 2015年4月1日 1:37
    2015年3月27日 8:25
    モデレータ
  • プロセスを監視するようなセキュリティソフトなどは動かしてませんか?
    • 編集済み hihijiji 2015年3月27日 8:29
    • 回答の候補に設定 星 睦美 2015年4月1日 1:39
    • 回答としてマーク 星 睦美 2015年4月1日 1:41
    2015年3月27日 8:29
  • Hoshinaです
    こんにちは

    20万件超で動作しています。

    OS:Windows 7 64ビット
    メモリ:6G
    .NET:4.5.1
    Visual Studio:2013 Community

    2つのプロジェクトをRelease版でコンパイルして、実行はVisual Studio上から行いました。
    20万回ですと、1時間近くかかります。

    今、23万回弱

    それでは

    • 回答としてマーク 星 睦美 2015年4月1日 1:38
    2015年3月27日 8:30
  • 佐祐理様
    hihijiji様
    trapemiya様
    Hoshina様

    ご回答ありがとうございます。

    皆様の環境で問題ないとすると自分の環境のせいかもしれません。

    会社のPCで実行しているので把握していないセキュリティソフトが動いているのかもしれないので調べてみます。

    2015年3月27日 8:59
  • C# のことは全く分かりませんけど。。。もしかしたら、問題の現象は環境に依存しているのかも。

    プロセスが終了すると、終了したプロセスに関連するリソースが全て解放されたタイミングで、そのプロセスが使用していたメモリ リソースが解放されます。
    (ちょっとへんな表現化もしれませんが。)
    たとえば、プロセス A が CreateProcessAsUser() API を使用してプロセス B を生成したとします。
    (さらにプロセス A が CreateRemoteThread() API をコールして、プロセス B にスレッドを作成したとします。)
    このあとプロセス B が (自発的に) に終了しても、もしプロセス A がプロセス B 生成時に取得したプロセス B のハンドルをクローズしていないと、プロセス B はそのハンドルが解放されるまで、終了した (ゾンビ) プロセスとしてメモリ上に残り続けることになります。
    (同じ理由で、他のプロセスがスレッド ハンドルをオープンしている場合も、同様の現象が起こります。)
    つまり、他のプロセスが終了したプロセスのリソースを握り続けている場合に、ご質問されているような状況が起こります。

    で、上記の現象がなぜ環境に依存して発生するかというと。。。
    セキュリティ ソフトの多くは、独自のサービスおよびドライバを組み込むことによって、その環境でのプロセスの生成/終了を監視しています。
    つまりセキュリティ ソフトが、監視対象プロセスのリソース ハンドルを適切なタイミングで解放しないと、上記で説明した状況が起こりうるわけです。
    なお、セキュリティ ソフトが組み込んでいるドライバの多くは、そのセキュリティ ソフトが組み込んだサービスを停止させるだけでは止まりません。
    ですので、セキュリティ ソフトの影響を検証するのであれば、アンインストールした状態で確認されることをお勧めします。

    蛇足ですが。。。
    上記で説明した状況が発生しているのかは、問題現象が発生したタイミングで完全メモリ ダンプを採取し、それを解析すれば判断できます。

    以上、参考になりましたら幸いに存じます。

    • 回答としてマーク 星 睦美 2015年4月1日 1:36
    2015年3月27日 9:21
  • お馬鹿様

    時間が取れず返信遅れてもしわけありません。

    一応試したことだけ書かせていただきます。

    自分の環境はwindows7 64bitでウィルスバスター コーポ―レートエディション(挙動監視コアサービス(64bit)が含まれているもの)がインストールされています。

    セキュリティソフトのアンインストールが許されていないためアンインストールした状態では確認できていません。

    ただ、できる範囲で試してみました。

    windows7 32bitでウィルスバスター コーポ―レートエディション(挙動監視コアサービス(32bit)が含まれているもの)がインストールされてるPCで実行したところ
    メモリの使用量は増えませんでした。

    windows2008 64bitでウィルスバスター コーポ―レートエディション(挙動監視コアサービスが含まれていないもの)がインストールされてるPCで実行したところ
    メモリの使用量は増えませんでした。

    なので確信はありませんが、ウィルスバスター コーポ―レートエディションの挙動監視コアサービス(64bit)が原因じゃないかと思っています。

    また、お馬鹿様の投稿からタスクマネージャーのプロセス項目のハンドル数を見てみたところwindows7 32bitの環境ではSystemプロセスのハンドル数が2千台だったのに対し問題の起きている環境ではSystemプロセスのハンドル数が20万台になっていました。

    今回はwindows7 32bitで問題が出なかったのでそちらで実行することにします。

    はっきりとしたことがわかったら投稿したいと思います。

    ご回答ありがとうございました。

     

    2015年4月7日 8:40
  • くだんのDLLを利用するプロセス(便宜上ワーカープロセスと呼称)を直接起動するのではなく、
    ワーカープロセスを管理するプロセス(便宜上管理プロセス)を別途用意しては如何でしょう?
    この管理プロセスに、ワーカープロセスの起動とワーカープロセスへの終了指示や、ジョブの受付と割り振をさせます。
    ワーカープロセスが常駐可能なら、ワーカープロセスの累積起動数は同時起動数と同数になります。
    常駐不可であってもジョブプールが空になってから時限終了させるようにすれば、そんなに累積起動数が増えることは無いと思います。
    2015年4月8日 8:32