Run subprocess in a thread and avoid freezing GUI?

Hello,

To avoid freezing the GUI, I’d trying to call subprocess() in a thread… but it doesn’t work :confused:

What’s the right way to run the call in a thread, and grab the output in the GUI?

Thanks for any help.

import sys,os, wx
import subprocess
import pyperclip
import threading

def call_subprocess(URL):
		TITLE = "Title not found" #default
		CMD_TITLE = fr"d:\youtube-dlp.exe --no-warnings --simulate --print '%(title)s' {URL}"
		p = subprocess.run(CMD_TITLE, text=True, stdout=subprocess.PIPE, creationflags=subprocess.CREATE_NO_WINDOW)
		if p.returncode != 0:
			output = f"ERROR TITLE: {URL}"
		else:
			output = p.stdout
		return output

class MyFrame(wx.Frame):
	def __init__(self, *args, **kwargs):  
		super().__init__(None, wx.ID_ANY,title='Bulk Youtube Download')

		self.Centre()

		panel = wx.Panel(self, -1)
		sizer = wx.BoxSizer(wx.VERTICAL)
		panel.SetSizer(sizer)

		self.label = wx.StaticText(panel, label="Title")
		sizer.Add(self.label, flag=wx.ALL, border=10)

		self.add_btn = wx.Button(panel, wx.ID_ANY, "Grab title")
		sizer.Add(self.add_btn,0, wx.EXPAND)
		self.add_btn.Bind(wx.EVT_BUTTON, self.OnAddTitleClick)

		panel.Layout()

	def OnAddTitleClick(self,event):
		URL = pyperclip.paste()

		thread1 = threading.Thread(target=call_subprocess, args=(URL,))
		
		thread1.start()

		#wait for thread to end and read output
		#SyntaxError: invalid syntax
		#return = thread1.join()
		thread1.join()
		#SyntaxError: invalid syntax
		return = thread1.result
		self.label.SetLabel(return)

		self.sizer.Layout()

app = wx.App()
MyFrame().Show()
app.MainLoop()


Edit: I guess it’s an old tool?

import sys,os, wx
import subprocess
import pyperclip
#pip install thread
import thread

def call_subprocess(URL):
		CMD_TITLE = fr"d:\youtube-dlp.exe --no-warnings --simulate --print '%(title)s' {URL}"
		p = subprocess.run(CMD_TITLE, text=True, stdout=subprocess.PIPE, creationflags=subprocess.CREATE_NO_WINDOW)
		return p.stdout if not p.returncode else f"ERROR: {URL}"

class ListBoxFrame(wx.Frame):
	def __init__(self, *args, **kwargs):  
		super().__init__(None, -1,title='Bulk Youtube Download')

		self.Centre()

		panel = wx.Panel(self, -1)
		sizer = wx.BoxSizer(wx.VERTICAL)

		self.label = wx.StaticText(panel, label="Title")
		sizer.Add(self.label, flag=wx.ALL, border=10)
		sizer.AddSpacer(10) 

		self.add_btn = wx.Button(panel, wx.ID_ANY, "Add URL")
		sizer.Add(self.add_btn,0, wx.EXPAND)
		self.add_btn.Bind(wx.EVT_BUTTON, self.OnAddButtonClick)

		panel.SetSizer(sizer)
		panel.Layout()

	def onLongRunDone(output):
		self.label.SetLabel(output)

	def call_subprocess(URL):
		CMD_TITLE = fr"c:\youtube-dlp.exe --no-warnings --simulate --print '%(title)s' {URL}"
		p = subprocess.run(CMD_TITLE, text=True, stdout=subprocess.PIPE, creationflags=subprocess.CREATE_NO_WINDOW)
		if p.returncode != 0:
			output = f"ERROR TITLE: {URL}"
		else:
			output = p.stdout
		return output
		wx.CallAfter(self.onLongRunDone(output))
	
	def OnAddButtonClick(self,event):
		URL = pyperclip.paste()

		#AttributeError: module 'thread' has no attribute 'start_new_thread'
		thread.start_new_thread(call_subprocess, (URL))

		self.sizer.Layout()
		# self.panel.Layout()  #Either works		

app = wx.App()
ListBoxFrame().Show()
app.MainLoop()


Edit: This seems to work:

import sys,os, wx
import subprocess
import pyperclip
import _thread

class ListBoxFrame(wx.Frame):
	def __init__(self, *args, **kwargs):  
		super().__init__(None, -1,title='Bulk Youtube Download')

		self.Centre()

		panel = wx.Panel(self, -1)
		self.sizer = wx.BoxSizer(wx.VERTICAL)

		self.label = wx.StaticText(panel, label="Title")
		self.sizer.Add(self.label, flag=wx.ALL, border=10)

		self.add_btn = wx.Button(panel, wx.ID_ANY, "Add URL")
		self.sizer.Add(self.add_btn,0, wx.EXPAND)
		self.add_btn.Bind(wx.EVT_BUTTON, self.OnAddButtonClick)

		panel.SetSizer(self.sizer)
		panel.Layout()

	def onLongRunDone(self, output):
		self.label.SetLabel(output)
		self.sizer.Layout()
		
	def call_subprocess(self, URL):
		CMD_TITLE = fr"d:\youtube-dlp.exe --no-warnings --simulate --print '%(title)s' {URL}"
		p = subprocess.run(CMD_TITLE, text=True, stdout=subprocess.PIPE, creationflags=subprocess.CREATE_NO_WINDOW)
		output = p.stdout if not p.returncode else f"ERROR: {URL}"
		wx.CallAfter(self.onLongRunDone,output)
	
	def OnAddButtonClick(self,event):
		URL = pyperclip.paste()

		new = _thread.start_new_thread(self.call_subprocess,(URL,))

app = wx.App()
ListBoxFrame().Show()
app.MainLoop()


Edit: Apparently, it’s recommended in Python3 to use the “threading” module instead of “_thread”:

import threading
...
	def onLongRunDone(self, output):
		self.label.SetLabel(output)

	def call_subprocess(self, URL):
		...
		self.SetTitle('Changed title in thread')
		wx.CallAfter(self.onLongRunDone,output)
	
	def OnAddButtonClick(self,event):
		URL = pyperclip.paste()

		x = threading.Thread(target=self.call_subprocess, args=(URL,))
		x.start()