Why is my output from a subprocess delayed, when i

2020-07-30 02:32发布

问题:

This an extension to my posting from yesterday, which is still unsolved: Why does my Python thread with subprocess not work as expected?

In the meantime I found some interesting details, so I decided to create a new posting. To bring it to the point: There are some issues, when a subprocess is generated out of a thread.

Platform: Windows 7 Enterprise, Python 3.6.1

In the following code I want to run a C-executable and get its output to stdout into a string. For test purposes the executable accepts two parameters: a delay and a filename (not used here). The program writes Sleep now to stdout, sleeps for the given number of milliseconds and finally writes after sleep and END.

This is the C-source of the executable:

int main(int argc, char *argv[])
{
    int sleep = 0;
    FILE * outfile = NULL;

    if (argc > 1)
    {
        sleep = atoi(argv[1]);
    }

    if (argc > 2)
    {
        outfile = fopen(argv[2], "w");
    }

    printf("Sleep now...\n");

    Sleep(sleep);

    if (outfile) fprintf(outfile, "output-1");
    printf("after sleep\n");
    printf("END\n");

    if (outfile) fclose(outfile);
    fclose(stdout);
    exit (0);
}

This is the Python code:

import subprocess
import threading
import time
import os
import sys


def worker_nok(*args):

    exe, delay, filename = args
    proc = subprocess.Popen([exe, delay, filename], shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    out, err = proc.communicate()
    print("%s:" % (filename,), out)
    sys.stdout.flush()


def worker(*args):
    exe, delay, filename = args
    flag = True
    proc = subprocess.Popen([exe, delay, filename], shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    while flag is True:
        str = proc.stdout.readline()
        if str == b"":
            print("%s: got EOF" % (filename,))
            flag = False
        else:
            print("%s:" % (filename,), str)
    print ("%s: END" % (filename,))
    sys.stdout.flush()


all = []    

"""
# 4 seconds
job_thread1 = threading.Thread(target = worker, args=(["../testapp.exe", "4000", "w4sec.txt"]))
job_thread1.start()
all.append(job_thread1)

# 8 seconds
job_thread2 = threading.Thread(target = worker, args=(["../testapp.exe", "8000", "w8sec.txt"]))
job_thread2.start()
all.append(job_thread1)
"""

# 4 seconds
job_thread3 = threading.Thread(target = worker_nok, args=(["../testapp.exe", "4000", "w4sec.nok"]))
job_thread3.start()
all.append(job_thread3)

# 8 seconds
job_thread3 = threading.Thread(target = worker_nok, args=(["../testapp.exe", "8000", "w8sec.nok"]))
job_thread3.start()
all.append(job_thread3)



for t in all:
    t.join()

The jobs have delays of 4, 8, and 1 seconds, respectively.

worker_nok calls proc.communicate() - it was my original approach, which is not working: When running the two jobs, I get the following output:

w4sec.nok: b'Sleep now...\r\nafter sleep\r\nEND\r\n'
w8sec.nok: b'Sleep now...\r\nafter sleep\r\nEND\r\n'

The whole bunch is received after 8 seconds. Instead I would expect

... 4 sec ...
w4sec.nok: b'Sleep now...\r\nafter sleep\r\nEND\r\n'

... 4 sec ...
w8sec.nok: b'Sleep now...\r\nafter sleep\r\nEND\r\n'

Although the process with 4 seconds is definitely finished, its output is available only after the second job has also finished.

This was the state from my last posting.

To find out whats wrong, I tried to replace communicate() by direct reading of stdout, which is implemented in the worker function. The EOF condition is identified when read returns an empty string. When calling it, I get:

... 4 seconds ...

w4sec.txt: b'Sleep now...\r\n'
w4sec.txt: b'after sleep\r\n'
w4sec.txt: b'END\r\n'

... 4 seconds ...

w8sec.txt: b'Sleep now...\r\n'
w8sec.txt: b'after sleep\r\n'
w8sec.txt: b'END\r\n'
w8sec.txt: got EOF
w8sec.txt: END
w4sec.txt: got EOF
w4sec.txt: END

However I would expect:

w4sec.txt: b'Sleep now...\r\n'
w8sec.txt: b'Sleep now...\r\n'

... 4 seconds ...

w4sec.txt: b'after sleep\r\n'
w4sec.txt: b'END\r\n'
w4sec.txt: got EOF
w4sec.txt: END

... 4 seconds ...   

w8sec.txt: b'after sleep\r\n'
w8sec.txt: b'END\r\n'
w8sec.txt: got EOF
w8sec.txt: END

The big Questions are:

(1) Why at all is there a delay before I get sleep now ? The executable makes this output available without delay. Is the stdout only available after the process has terminated?

(2) More important: Why is the EOF for the 4sec available only after the 8sec call has finished?

I hope this input makes clear, why the original version showed the observed behavior: The stdout is in the EOF-condition much too late and leaves the call to communicate() blocking!

I appreciate any inputs, since I'm working on that already for more than 12 hours...