C#からPythonを実行する方法(非同期対応、出力をリアルタイムで取得可能)

初めに

最近、C#テキストエディタを自作していたのですが、コードを書きながらデバッグをできるようにC#からPythonを実行しようとしたところ、かなり躓いたので解決方法を載せることにしました。

最初に試したこと

まず、検索をしてみると、C#のProcessクラスから、Pythonを実行することができることを知ったので、試しました。

//fileName = 実行したいファイルのパス
public void Python_Start(string fileName) {

            var myProcess = new Process
            {
                StartInfo = new ProcessStartInfo("python.exe")
                {
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    Arguments = fileName
                }
            };
 
            myProcess.Start();
            StreamReader myStreamReader = myProcess.StandardOutput;
            string myString = myStreamReader.ReadLine();
            myProcess.WaitForExit();
            myProcess.Close();
 
            Console.WriteLine("実行結果" + myString);
}

この方法でもPythonは実行できたのですが...以下の理由でデバッグ機能としては使えませんでした。

非同期で実行できない(実行すると固まってほかの操作ができなくなってしまう)

Pythonの処理が終わった瞬間に結果が出力される(時間がかかる処理の場合、リアルタイムで出力を取得できない)

その後、いろいろと調べて、次のコードができました

完成したコード

先ほどの2つの問題を解消したコードです。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;

namespace Debuger
{
    class Debug_Python
    {
        
        /// <summary>
        /// デバッグを開始
        /// </summary>
        /// <param name="fileName"></param>
        public void Start(string fileName)
        {
            Console.WriteLine("Start");

            Process p = new Process();
            p.OutputDataReceived += new DataReceivedEventHandler(DataReceived);
            p.ErrorDataReceived += new DataReceivedEventHandler(DataReceived);
            p.Exited += new EventHandler(Exited);

            p.StartInfo.FileName = "python";
            p.StartInfo.Arguments = "-u \"" + fileName + "\"";
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.RedirectStandardOutput = true;
            p.StartInfo.RedirectStandardError = true;
            p.StartInfo.RedirectStandardInput = true;
            p.EnableRaisingEvents = true;
            p.StartInfo.CreateNoWindow = true;

            p.Start();
            p.BeginOutputReadLine();
            p.BeginErrorReadLine();
        }

        //出力を受け取る
        private void DataReceived(object sender, DataReceivedEventArgs e)
        {
            if (string.IsNullOrEmpty(e.Data)) return;
            Console.WriteLine(e.Data);
        }

        //処理が終わった時
        private void Exited(object sender, EventArgs e)
        {
            Console.WriteLine("End");
        }
    }
}

説明

まず、

 Process p = new Process();
 p.OutputDataReceived += new DataReceivedEventHandler(DataReceived);
 p.ErrorDataReceived += new DataReceivedEventHandler(DataReceived);
 p.Exited += new EventHandler(Exited);

Processクラスのインスタンスを作成した後に、それぞれのイベントハンドラを登録しています。

簡単に説明すると、p.OutputDataReveived += new DataReceivedEventHandler(DataReceived); でPythonからの出力を受け取った時に、DataReceivedというメソッドに値を渡すよという宣言をしています。

DataReceived メソッド

private void DataReceived(object sender, DataReceivedEventArgs e)
{
 if (string.IsNullOrEmpty(e.Data)) return;
 Console.WriteLine(e.Data);
}

e.Dataが実行した結果です。

他も同じように、ErrorDataReceivedはエラーが起きた時、Exitedはプロセスが終了した時にそれぞれのメソッドに値を渡しています。 これで、非同期で実行することが可能です!

次にProcessの設定をします。

            //Pythonを実行するよ
            p.StartInfo.FileName = "python";

            //このファイルを実行するよ
            p.StartInfo.Arguments = "-u \"" + fileName + "\"";

            //シェルを使用しないよ
            p.StartInfo.UseShellExecute = false;

            //出力を受け取るよ
            p.StartInfo.RedirectStandardOutput = true;

            //エラーを受け取るよ
            p.StartInfo.RedirectStandardError = true;

            //終了した時知らせるよ
            p.EnableRaisingEvents = true;

            //ウィンドウは表示しないよ
            p.StartInfo.CreateNoWindow = true;

簡単に言うとコメントのような設定をしています。

結構大事なのがここ

p.StartInfo.Arguments = "-u \"" + fileName + "\"";

ここで、実行したいファイルを指定するのですが、-uオプションを付けないと出力がリアルタイムで取得できません。 これでかなり悩みました(´・ω・`)

後はStartメソッドを呼び出せばOKです。

var python = new Debug_Python();
python.Start(@"\scr\test.py"); //実行したいファイルのパスを渡す

試しに、Visual Studioの新しいプロジェクトからコンソールアプリケーションを選択して、デスクトップにあるPythonファイルを実行してみます。

C:\Users\user1\Desktop\test.py

import time

print("python start")
#10秒待つ
time.sleep(10)
print("python end")

C# コンソールアプリ

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Debuger
{
    class Program
    {
        static void Main(string[] args)
        {
            var python = new Debug_Python();
            python.Start(@"C:\Users\user1\Desktop\test.py");
            Console.WriteLine("非同期チェック");
            while (true) { }
        }
    }
}

Debug_Python.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;

namespace Debuger
{
    class Debug_Python
    {

        /// <summary>
        /// デバッグを開始
        /// </summary>
        /// <param name="fileName"></param>
        public void Start(string fileName)
        {
            Console.WriteLine("Start");

            Process p = new Process();
            p.OutputDataReceived += new DataReceivedEventHandler(DataReceived);
            p.ErrorDataReceived += new DataReceivedEventHandler(DataReceived);
            p.Exited += new EventHandler(Exited);

            p.StartInfo.FileName = "python";
            p.StartInfo.Arguments = "-u \"" + fileName + "\"";
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.RedirectStandardOutput = true;
            p.StartInfo.RedirectStandardError = true;
            p.StartInfo.RedirectStandardInput = true;
            p.EnableRaisingEvents = true;
            p.StartInfo.CreateNoWindow = true;

            p.Start();
            p.BeginOutputReadLine();
            p.BeginErrorReadLine();
        }

        //出力を受け取る
        private void DataReceived(object sender, DataReceivedEventArgs e)
        {
            if (string.IsNullOrEmpty(e.Data)) return;
            Console.WriteLine(e.Data);
        }

        //処理が終わった時
        private void Exited(object sender, EventArgs e)
        {
            Console.WriteLine("End");
        }
    }
}

結果

Start
非同期チェック
python start
//10秒後
python end
End

きちんと非同期で実行されていて、結果もリアルタイムで取得できています。

ちなみに

同じような方法で、Node.jsからJavaScriptを実行することも可能です。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;

namespace Debuger
{
    class Debug_Python
    {
        
        /// <summary>
        /// デバッグを開始
        /// </summary>
        /// <param name="fileName"></param>
        public void Start(string fileName)
        {
            Console.WriteLine("Start");

            Process p = new Process();
            p.OutputDataReceived += new DataReceivedEventHandler(DataReceived);
            p.ErrorDataReceived += new DataReceivedEventHandler(DataReceived);
            p.Exited += new EventHandler(Exited);

            p.StartInfo.FileName = "node";
            p.StartInfo.Arguments = fileName;
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.RedirectStandardOutput = true;
            p.StartInfo.RedirectStandardError = true;
            p.StartInfo.RedirectStandardInput = true;
            p.EnableRaisingEvents = true;
            p.StartInfo.CreateNoWindow = true;

            p.Start();
            p.BeginOutputReadLine();
            p.BeginErrorReadLine();
        }

        //出力を受け取る
        private void DataReceived(object sender, DataReceivedEventArgs e)
        {
            if (string.IsNullOrEmpty(e.Data)) return;
            Console.WriteLine(e.Data);
        }

        //処理が終わった時
        private void Exited(object sender, EventArgs e)
        {
            Console.WriteLine("End");
        }
    }
}

最後に

C#からPythonを実行する方法を紹介しました。 いかがだったでしょうか。何かのお役に立てれば幸いです。 間違っているところや、ここわかりづらい!などありましたら、ぜひコメントお願いします。 次はPythonとかC#とかで何かしようかなと思ってます