最小的可重現示例
我創建了一個最小的可重現示例,可以在 Github 上克隆并輕松運行以進行測驗:https ://github.com/VirxEC/python-no-run
如果你不想去 GitHub,那么代碼片段將在這篇文章的底部。
盡快描述問題
在 Windows 上從 Rust 啟動 Python 時,(Linux 作業正常)從標準輸入讀取會阻止 Python 子行程啟動更多 Python 子行程。添加一個快速sleep(1)讓子行程啟動并且它作業正常,但如果子行程需要超過 1 秒才能啟動,那么我就不走運了。如果它需要不到 1 秒,那么我等待的時間比我必須的要長。所以,使用sleep是我現在實施的快速補丁,但我需要一個合適的解決方案。
該問題,更詳細地描述
何時以及為什么
僅當僅在 Windows 上從 Rust 啟動 Python 時才會發生此問題,并且如果您正常運行 Python,那么一切都會按預期作業。但是,我的應用程式不是 Python 應用程式,它是使用 Tauri 和 Python 子組件的 Rust GUI,因為有一個大型庫,它是純 Python 的,尚未移植到 Rust ......并且可能永遠不會被移植到它的大尺寸。
這也意味著我不能在生成子行程時從主 Rust 行程中獲取其他不相關的命令,因為它只是不讀取標準輸入并尋找它們。在我的真實程式中,我從執行緒啟動一個新的“執行緒”,因此理論上可以在子行程啟動時讀取標準輸入。現在,在生成這些行程時,我不能讓 stdin 被讀取,這只會增加不必要的額外復雜性。
應該發生什么與確實發生了什么
問題是“Hello World”沒有收到垃圾郵件,因為子行程產生但沒有開始執行。我一直在這樣做,我對正在發生的事情感到茫然。我知道它與標準輸入有關,但我的程式使用標準輸入和標準輸出在 Rust 主行程和 Python 行程之間進行通信。
從主執行緒上的標準輸入以某種方式讀取會阻止子行程一路啟動。我知道這一點,因為如果您shell=True在回圈退出后使用 then 生成子行程,“Hello World”就會收到垃圾郵件。
此外,如果我sleep(1)在生成子行程后立即添加,那么一切正常!耶!除了這遠非理想。這就像一個創可貼,對傷口來說還不夠大,但至少它可以覆寫一些。我已經在我的程式中臨時實施了這個解決方案(特別sleep(3)是針對每個生成的子行程),但是可能需要生成許多行程,這最終會花費太長時間。此外,一個程序仍然可能需要超過 3 秒的時間才能啟動,如果發生這種情況,我會很不走運。
無聊的代碼(僅限 Windows!)
我已經洗掉了匯入陳述句等,因為它們不應該非常相關。如果您有興趣,它們仍然可以在包含整個最小可重現示例的 GitHub 存盤庫中找到。
main.rs
// Setup command
let mut command = Command::new("python");
command.args(["-u", "-c", "from main import start; start()"]);
command.current_dir(std::env::current_dir()?.join("python"));
// Pipe stdin so we can issue commands to tell Python what we want it to do
command.stdin(Stdio::piped());
// Spawn the process & take ownership of stdin
let mut child = command.spawn()?;
let mut stdin = child.stdin.take().unwrap();
// Give a second to make 100% sure Python has started
sleep(Duration::from_secs(1));
// Issue start command to Python, wait 5 seconds then tell it to stop
println!("Writing start");
stdin.write_all(b"start | \n")?;
sleep(Duration::from_secs(5));
println!("Writing stop");
stdin.write_all(b"stop | \n")?;
// Make sure the process has exited before we exit
child.wait()?;
main.py
def start():
procs = []
while True:
print("Listening for new command...")
command = sys.stdin.readline()
params = command.split(" | ")
print(f"Got command: {command}")
if params[0] == "stop":
print("Stopping...")
# Break from loop
break
elif params[0] == "start":
print("Starting...")
# Spawn the subprocess
proc = subprocess.Popen([sys.executable, "say-hi.py"])
# Add the subprocess to the list so we can kill it later
procs.append(proc)
print("Shutting down")
# Terminate processes
for proc in procs:
proc.terminate()
say-hi.py
while True:
print(f"Hello world!", flush=True)
sleep(1)
我的跑步程序
這類似于README.mdGitHub 存盤庫中的說明。
在根檔案夾中
- 運行
cargo r,看看什么都沒有發生 - 在
python/main.py更改Popen([sys.executable, "say-hi.py"])為Popen([sys.executable, "say-hi.py"], shell=True) - 運行
cargo r并查看“Hello world”在發出停止命令時會發送垃圾郵件并且永遠不會停止
在python檔案夾中
shell可以是True或False,沒關系- 運行
python -c "from main import start; start()"以啟動行程 - 準確輸入
start |并看到“Hello world”被發送垃圾郵件 stop |準確輸入,看到“Hello world”停止發送垃圾郵件,行程全部成功退出
uj5u.com熱心網友回復:
具有默認引數的 subprocess.Popen 從 Windows 上的作業系統請求 stdout 和 stdin 的句柄......這似乎因某種原因卡住或失敗。
無論如何,通過父母的標準輸出或管道似乎可以在 Windows 上修復它,只需替換
proc = subprocess.Popen([sys.executable, "say-hi.py"])
和
proc = subprocess.Popen([sys.executable, "say-hi.py"], stdin=subprocess.PIPE, stdout=sys.stdout, stderr=sys.stderr)
它只是將父級的標準輸出檔案 ID 傳遞給子級以使用。
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/523970.html
