10
16

More than 3 years have passed since last update.

C#からPythonスクリプトを同期的に実行する

Last updated at Posted at 2020-01-27

はじめに

これまでC#とPythonの連携について似たようなネタをいくつか書いてきた。

C# GUIアプリケーションからPythonスクリプトを実行する」では、Pythonスクリプトを実行し、処理が終了するまで進捗状況を確認できるようにした。

C# GUIからPythonのコードを実行する」では、Pythonスクリプトではなく、画面上からPythonコードを入力できるようにしてみた。

そして「C# GUIからバックグラウンドでプロセスを実行し管理する」では、Pythonを実行した後、終了まで待たずに別の作業をしつつ、気になったときに実行状況を確認できる方法について調べた。

そこで、今回がこのシリーズ最後のネタになる。

そもそもGUIなんかいらんから、Pythonのスクリプトを実行して、その処理結果を受け取るようなメソッドとか作りたいんだけど、どうすんの?

要するにGUIを介さず、Pythonスクリプトを開始し、実行が終わるまで次の処理に行かずにそこで待ち(同期をとる)、その後結果を取得するという形の呼び出しだ。

ソース

はい、どん。

PythonExecutor.cs
using System;
using System.Diagnostics;
using System.IO;
using System.Text;

namespace ChemShow
{
    public class PythonExecutor
    {
        public String FileName { get; set; }
        public String WorkingDirectory { get; set; }
        public String Arguments { get; set; }
        public String InputString { get; set; }
        public String StandardOutput { get; set; }
        public int ExitCode { get; set; }

        private StringBuilder standardOutputStringBuilder = new StringBuilder();

        public PythonExecutor()
        {

        }

        /// 実行ボタンクリック時の動作
        public  void Execute()
        {

            ProcessStartInfo psInfo = new ProcessStartInfo();
            psInfo.FileName = this.FileName;
            psInfo.WorkingDirectory = this.WorkingDirectory;
            psInfo.Arguments = this.Arguments;

            psInfo.CreateNoWindow = true;
            psInfo.UseShellExecute = false;
            psInfo.RedirectStandardInput = true;
            psInfo.RedirectStandardOutput = true;
            psInfo.RedirectStandardError = true;

            // Process p = Process.Start(psInfo);
            Process p = new System.Diagnostics.Process();
            p.StartInfo = psInfo;
            p.OutputDataReceived += p_OutputDataReceived;
            p.ErrorDataReceived += p_ErrorDataReceived;

            // プロセスの実行
            p.Start();

            // 標準入力への書き込み
            using (StreamWriter sw = p.StandardInput)
            {
                sw.Write(InputString);
            }

            //非同期で出力とエラーの読み取りを開始
            p.BeginOutputReadLine();
            p.BeginErrorReadLine();

            // 終わるまでまつ
            p.WaitForExit();
            this.ExitCode = p.ExitCode;
            this.StandardOutput = standardOutputStringBuilder.ToString();
        }

        /// <summary>
        /// 標準出力データを受け取った時の処理
        /// </summary>
        void p_OutputDataReceived(object sender,
            System.Diagnostics.DataReceivedEventArgs e)
        {
            //processMessage(sender, e);
            if (e != null && e.Data != null && e.Data.Length > 0)
            {
                standardOutputStringBuilder.Append(e.Data + "\n");
            }
        }

        /// <summary>
        /// 標準エラーを受け取った時の処理
        /// </summary>
        void p_ErrorDataReceived(object sender,
            System.Diagnostics.DataReceivedEventArgs e)
        {
            //必要な処理を書く
        }
    }
}

ソース解説

目新しいことはなく、プロセスを開始したらそのままWaitForExit()を実行しているだけ。そうするとプロセスが終了するまで次にいかない。これにより同期的な呼び出しが実現できる。実行終了後、終了コードや標準出力のデータをプロパティから取得できるようにしている。

使い方

以下はスクリプトの標準入力にデータを与え、標準出力に出力された結果を受け取る例である。

PythonExecutor pe = new PythonExecutor();
pe.FileName=@"c:\python3.6\bin\python.exe";
pe.WorkingDirectory = @"c:\tmp";
pe.Arguments =@"C:\ChemShow\python\hist.py";
pe.InputString = "1,2,3,4,5,6,7,8,9,10"; //標準入力に与えるデータ
pe.Execute(); //実行
Console.WriteLine(pe.StandardOutput); //標準出力の結果を出力

2020/2/24 修正

プロセスを2回スタートさせるという致命的バグがあったため以下の通り修正。ご迷惑おかけしました。

// Process p = Process.Start(psInfo);
Process p = new System.Diagnostics.Process();
p.StartInfo = psInfo;

おわりに

要するに、今までに出てきた全ての呼び出し方に対応したコンポーネントをいい加減作れよという話。

後、タイトルはPythonスクリプトにしているが、コマンドラインから実行できるものであれば同じですね。

10
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
16