Ответ 1
Этот ответ очень прост! (Мне просто потребовалось несколько дней, чтобы разобраться.)
В сочетании с PyGTK idle_add() вы можете создать AutoJoiningThread. Общий код является тривиальным:
class AutoJoiningThread(threading.Thread):
def run(self):
threading.Thread.run(self)
gobject.idle_add(self.join)
Если вы хотите сделать больше, чем просто присоединиться (например, собирать результаты), вы можете расширить вышеуказанный класс, чтобы испускать сигналы при завершении, как это сделано в следующем примере:
import threading
import time
import sys
import gobject
gobject.threads_init()
class Child:
def __init__(self):
self.result = None
def play(self, count):
print "Child starting to play."
for i in range(count):
print "Child playing."
time.sleep(1)
print "Child finished playing."
self.result = 42
def get_result(self, obj):
print "The result was "+str(self.result)
class AutoJoiningThread(threading.Thread, gobject.GObject):
__gsignals__ = {
'finished': (gobject.SIGNAL_RUN_LAST,
gobject.TYPE_NONE,
())
}
def __init__(self, *args, **kwargs):
threading.Thread.__init__(self, *args, **kwargs)
gobject.GObject.__init__(self)
def run(self):
threading.Thread.run(self)
gobject.idle_add(self.join)
gobject.idle_add(self.emit, 'finished')
def join(self):
threading.Thread.join(self)
print "Called Thread.join()"
if __name__ == '__main__':
print "Creating child"
child = Child()
print "Creating thread"
thread = AutoJoiningThread(target=child.play,
args=(3,))
thread.connect('finished', child.get_result)
print "Starting thread"
thread.start()
print "Running mainloop (Ctrl+C to exit)"
mainloop = gobject.MainLoop()
try:
mainloop.run()
except KeyboardInterrupt:
print "Received KeyboardInterrupt. Quiting."
sys.exit()
print "God knows how we got here. Quiting."
sys.exit()
Результат приведенного выше примера будет зависеть от порядка выполнения потоков, но он будет похож на:
Creating child Creating thread Starting thread Child starting to play. Child playing. Running mainloop (Ctrl+C to exit) Child playing. Child playing. Child finished playing. Called Thread.join() The result was 42 ^CReceived KeyboardInterrupt. Quiting.
Невозможно создать AutoJoiningProcess таким же образом (потому что мы не можем вызывать idle_add() для двух разных процессов), однако мы можем использовать AutoJoiningThread, чтобы получить то, что хотим:
class AutoJoiningProcess(multiprocessing.Process):
def start(self):
thread = AutoJoiningThread(target=self.start_process)
thread.start() # automatically joins
def start_process(self):
multiprocessing.Process.start(self)
self.join()
Чтобы продемонстрировать AutoJoiningProcess, вот еще один пример:
import threading
import multiprocessing
import time
import sys
import gobject
gobject.threads_init()
class Child:
def __init__(self):
self.result = multiprocessing.Manager().list()
def play(self, count):
print "Child starting to play."
for i in range(count):
print "Child playing."
time.sleep(1)
print "Child finished playing."
self.result.append(42)
def get_result(self, obj):
print "The result was "+str(self.result)
class AutoJoiningThread(threading.Thread, gobject.GObject):
__gsignals__ = {
'finished': (gobject.SIGNAL_RUN_LAST,
gobject.TYPE_NONE,
())
}
def __init__(self, *args, **kwargs):
threading.Thread.__init__(self, *args, **kwargs)
gobject.GObject.__init__(self)
def run(self):
threading.Thread.run(self)
gobject.idle_add(self.join)
gobject.idle_add(self.emit, 'finished')
def join(self):
threading.Thread.join(self)
print "Called Thread.join()"
class AutoJoiningProcess(multiprocessing.Process, gobject.GObject):
__gsignals__ = {
'finished': (gobject.SIGNAL_RUN_LAST,
gobject.TYPE_NONE,
())
}
def __init__(self, *args, **kwargs):
multiprocessing.Process.__init__(self, *args, **kwargs)
gobject.GObject.__init__(self)
def start(self):
thread = AutoJoiningThread(target=self.start_process)
thread.start()
def start_process(self):
multiprocessing.Process.start(self)
self.join()
gobject.idle_add(self.emit, 'finished')
def join(self):
multiprocessing.Process.join(self)
print "Called Process.join()"
if __name__ == '__main__':
print "Creating child"
child = Child()
print "Creating thread"
process = AutoJoiningProcess(target=child.play,
args=(3,))
process.connect('finished',child.get_result)
print "Starting thread"
process.start()
print "Running mainloop (Ctrl+C to exit)"
mainloop = gobject.MainLoop()
try:
mainloop.run()
except KeyboardInterrupt:
print "Received KeyboardInterrupt. Quiting."
sys.exit()
print "God knows how we got here. Quiting."
sys.exit()
Результирующий результат будет очень похож на приведенный выше пример, за исключением того, что на этот раз у нас есть как соединение процесса, так и сопутствующий поток:
Creating child Creating thread Starting thread Running mainloop (Ctrl+C to exit) Child starting to play. Child playing. Child playing. Child playing. Child finished playing. Called Process.join() The result was [42] Called Thread.join() ^CReceived KeyboardInterrupt. Quiting.
К сожалению:
- Это решение зависит от gobject, из-за использования idle_add(). gobject используется PyGTK.
- Это не настоящие отношения родитель/ребенок. Если один из этих потоков запущен другим потоком, то он тем не менее будет объединен потоком, выполняющим mainloop, а не родительский поток. Эта проблема справедлива и для AutoJoiningProcess, за исключением того, что я предполагаю, что будет выбрано исключение.
Таким образом, чтобы использовать этот подход, было бы лучше всего создавать потоки/процессы только из mainloop/GUI.