c# - How to synchronize writing and async reading from interactive process's std's -


i trying build wpf application makes use of pythons dynamic interpreter , eval function. edit: gave more detailed description here in simple words, want able following:

string expression = console.readline("please enter expression"); if (evaluatewithpythonprocess(expression) > 4) {   // } else {   // else } 

as program uses functionality during it's entire lifetime, not able exit python process each time want start evaluation. consequence, stdin, stdout , stderr streams remain open time.

i able start interactive python.exe using process class , 2 corresponding onoutputdatareceived , onerrordatareceived methods transfer data stdout , stderr stringbuilders:

// create python process startupinfo object                 processstartinfo _processstartinfo = new processstartinfo(pythonhelper.pathtopython + "python.exe");                  // python uses "-i" run in interactive mode                 _processstartinfo.arguments = "-i";                  // start python process, don't show (console) window                 _processstartinfo.windowstyle = processwindowstyle.minimized;                 _processstartinfo.createnowindow = true;                  // enable redirection of python process std's                 _processstartinfo.useshellexecute = false;                  _processstartinfo.redirectstandardoutput = true;                 _processstartinfo.redirectstandardinput = true;                 _processstartinfo.redirectstandarderror = true;                  // create python process object , apply startupinfos above                 _pythonprocess = new process();                 _pythonprocess.startinfo = _processstartinfo;                  // start process, _hasstarted indicates if process started (true) or if reused (false, running)                      _pythonprocess.outputdatareceived += new datareceivedeventhandler(onoutputdatareceived);                     _pythonprocess.errordatareceived += new datareceivedeventhandler(onerrordatareceived);                     bool _hasstarted = _pythonprocess.start();                      _pythonprocess.beginoutputreadline();                     _pythonprocess.beginerrorreadline();                     _input = _pythonprocess.standardinput; 

however, cannot manage synchronize application asynchronous gathering of results. 2 on*datareceived() methods called asynchronously, not know if python has finished evaluation of expression. possible solution create wait handle before sending commands pythons stdin can wait afterwards. both onoutputdatareceived , onerrordatareceived methods signal handle. however, somehow obscured intended behaviour of python:

                // example a: import sys modul in python                  // cause neither output, nor error:                 _input.writeline("import sys");                  // example b: writing pythons stderr or stdout results in error , output, how can tell if error occured?                 _input.writeline("sys.stderr.write('initialized stderr')");                  _input.writeline("sys.stdout.write('initialized stdout')");                  // example c: intended use, how can tell if evaluation has finished succesfully?                 _input.writeline("print(4+7)");                  // example d: simple typo might lead unforeseeable errors how can tell if evaluation has finished succesfully?                 _input.writeline("pr int(4+7)"); 

i found solution mind workaround specific scenario , not general solution.

based on comments @peter tried figure out how "human solve problem"

  1. i have make sure child process communicates parent process via stdout only.
  2. i have create message based protocol ensures child python process report whether has received , understood message sent parent c# process, , if reports evaluated value of expression.
  3. i have find way synchronize subsequent writing , reading

points 1 , 2 achieved defining python method target parent-process. make use of pythons exception handling routines detect errors , prevent writing stderr follows :

def pythoneval(expression):     "takes expression , uses eval function, wrapped in try-except-statement, inform parent process value of expression"     try:       print(eval(expression))       print('child: done')     except:       print('child: error')     return 

this definition can applied within c# application wrapping python code in string , passing child process's stdin:

childinput = childprocess.standardinput;  childinput.writeline("def pythoneval(expression):\n" +       "\t\"takes expression , uses eval function, wrapped in try-except-clause, inform lmt outcome of expression\"\n" +      "\ttry:\n" +         "\t\tprint(eval(expression))\n" +          "\t\tprint('" + _pythonsuccessmessage + "')\n" +      "\texcept:\n" +          "\t\tprint('" + _pythonerrormessage + "')\n" +      "\treturn\n" +      "\n"); // last newline character important, ends definition of method if python in interactive mode  

if want evaluate expression of child process, parent process has wrap expression in corresponding call of python method:

childinput.writeline("pythoneval('" + expression + "')"); 

this in cases result in message child process's stdout has last line of form "child: done|error", can compare , set boolean flag _haserror in latter case. entire message passed stringbuilder outputmessage.

when child process sends message stdout, c# process object's outputdatareceivedevent fired , data read asynchronously onoutputdatareceived method. in order synchronize asnyc read operations of process, use autoresetevent. allows both synchronize parent c# process python process, , prevent deadlocks using autoresetevent.waitone(int timeout) overload.

the autoresetevent reset manually in specific method sends command python, automatically after waitone has completed (before timeout occured), , set manually in async onoutputdatareceived() method follows:

private autoresetevent _outputresetevent; private bool _haserror; private stringbuilder _outputmessage;  private void evaluatewithpython(string expression) {     // set _outputresetevent unsignalled state     _outputresetevent.reset();      // reset _haserror,      _haserror = true;      // write command python, using dedicated method     childinput.writeline("pythoneval('" + expression + "')"); // ' chars again important, eval method in python takes string, indicated 's in python      // wait python write stdout, captured onoutputdatareceived (below) , sets _outputresetevent signalled stat     bool _timeoutoccured = _outputresetevent.waitone(5000);      // give onoutputdatareceived time finish     task.delay(200).wait();         }  private void onoutputdatareceived(object sender, datareceivedeventargs e) {     if (e == null)     {         throw new argumentnullexception();     }      if (e.data != null)     {         // pass message stringbuilder line line, onoutputdatareceived called line line         _outputmessage.appendline(e.data);          // check end of message, in cases of form "child: done|error"         // in case, set errorflag if needed , signal autoresetevent         if (e.data.equals("child: error"))         {             _haserror = true;             _outputresetevent.set();         }         else if (e.data.equals("child: done"))         {             _haserror = false;             _outputresetevent.set();         }     }     else     {         // todo: reach point if child python process ends , stdout closed (?)         _outputresetevent.set();     }   } 

with approach able call evaluatewithpython , can synchronously:

  • check if python finished before timeout occured (and react in way if hadn't)
  • if no timeout occured, know _haserror tells if evaluation succesful
  • if case, outputmessage contains result in next-but-last line.

in order cope overseen problems, write onerrordatareceived method, capture unhandled exceptions , syntax errors python process , could, example, throw exception should mind never happen.


Comments

Popular posts from this blog

java - Suppress Jboss version details from HTTP error response -

gridview - Yii2 DataPorivider $totalSum for a column -

Sass watch command compiles .scss files before full sftp upload -