Home > other >  .py file executed by C# process not waiting to finish
.py file executed by C# process not waiting to finish

Time:04-02

I want to run .py file from my C# project, and get the result. The python script is making an API request, and returns an auth_key token, which I want to use in my C# code. The only problem is that, for some reason the C# code doesn't wait for the process to finish, and thus that not every account has auth_key. Here is my C# code.

private static void GenerateTokens()
{
    var url = ConfigurationManager.AppSetting[GeSettingsNode()   ":ip"];

    for (int i = 0; i < accounts.Count; i  )
    {
         ProcessStartInfo start = new ProcessStartInfo();
         start.FileName = ConfigurationManager.AppSetting["PythonPath"];
         start.Arguments = string.Format($"python_operation_processor.py {accounts[i].client_key_id} {accounts[i].key_sercret_part} {url}");
         start.UseShellExecute = false;
         start.RedirectStandardOutput = true;
         Process process = Process.Start(start);
         using (StreamReader reader = process.StandardOutput)
         {
              accounts[i].auth_key = reader.ReadToEnd().Trim();
         }
     }
}

And here is my Python script ( python_operation_processor.py )that's making the API requests.

if __name__ == '__main__':
    client_key_id = sys.argv[1]
    client_secret = sys.argv[2]
    API_URL = sys.argv[3]

    nonce = str(uuid.uuid4())

    d = datetime.datetime.now() - datetime.timedelta(hours=3)
    timestamp = d.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3]   'Z'

    signature = b64encode(hmac.new(b64decode(client_secret), msg=bytes(client_key_id   nonce   timestamp, 'utf-8'),
                                   digestmod=hashlib.sha256).digest()).decode('utf-8')

    r = requests.post(API_URL   '/v1/authenticate',
                      json={'client_key_id': client_key_id, 'timestamp': timestamp, 'nonce': nonce,
                            'signature': signature})
    if r.status_code != 200:
        raise Exception('Failed to authenticate: '   r.text)

    auth_token = r.json()['token']
    print(auth_token)

Do you have any idea, how I can wait for the execution of every process, and get the token for every account ?

CodePudding user response:

I recently created something similar and ended up with this because, whilst waiting for the process is easy, it is tricky to get the output stream filled correctly.

The method presented also allow you to display the output into a textblock or similar in your application.

If you use it like this, the token will be written to the StringBuilder, and used as return value.

private async Task<string> RunCommand(string fileName, string args)
{
    var timeoutSignal = new CancellationTokenSource(TimeSpan.FromMinutes(3));
    ProcessStartInfo start = new ProcessStartInfo();
    start.FileName = fileName;
    start.Arguments = string.Format("{0}", args);
    start.RedirectStandardOutput = true;
    start.RedirectStandardError = true;
    start.UseShellExecute = false;
    start.CreateNoWindow = true;
    
    var sb = new StringBuilder();
    using (Process process = new Process())
    {
        process.StartInfo = start;
        process.OutputDataReceived  = (sender, eventArgs) =>
        {
            sb.AppendLine(eventArgs.Data); //allow other stuff as well
        };
        process.ErrorDataReceived  = OnProcessOnErrorDataReceived;

        if (process.Start())
        {
            process.EnableRaisingEvents = true;
            process.BeginOutputReadLine();
            process.BeginErrorReadLine();

            await process.WaitForExitAsync(timeoutSignal.Token);
            //allow std out to be flushed
            await Task.Delay(100);
        }
    }
    return sb.ToString();
}

To render this to a textblock in a UI application, you'll need to:

  • implement an event which signals a new line has been read, which means forwarding the process.OutputDataReceived event.
  • if your thinking about a live feed, make sure you flush the stdio buffer in python setting flush to true: print(""hello world"", flush=True)

If you're using an older .net version; you can implement the WaitForExitAsync as described here: https://stackoverflow.com/a/17936541/2416958 as an extention method:

public static class ProcessHelpers
{
    public static Task<bool> WaitForExitAsync(this Process process, TimeSpan timeout)
    {
        ManualResetEvent processWaitObject = new ManualResetEvent(false);
        processWaitObject.SafeWaitHandle = new SafeWaitHandle(process.Handle, false);

        TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();

        RegisteredWaitHandle registeredProcessWaitHandle = null;
        registeredProcessWaitHandle = ThreadPool.RegisterWaitForSingleObject(
            processWaitObject,
            delegate(object state, bool timedOut)
            {
                if (!timedOut)
                {
                    registeredProcessWaitHandle.Unregister(null);
                }

                processWaitObject.Dispose();
                tcs.SetResult(!timedOut);
            },
            null /* state */,
            timeout,
            true /* executeOnlyOnce */);

        return tcs.Task;
    }
}
  • Related