George
George

Reputation: 1

Multi-threading while using john zelles graphics module (Python)

I have been using the John Zelle graphics module for some time now, however this is my first time attempting to use the threading module. I am trying to make multiple objects (circles) that have different starting positions move along my screen simultaneously. This code will also contain recursion and jumping to multiple functions in its final product.

Here is an example of what I am trying to do:

from graphics import *
from random import randint
import time
import threading

class Particle:
    
    def __init__(self):
        self.xpos   = randint(0,50)
        self.ypos   = randint(0,50)
        
        self.graphic = Circle(Point(self.xpos,self.ypos),5)    
        self.graphic.draw(window)
        
    def move(self):
        for step in range(5):
            self.xpos += 1
            self.ypos += 1
            self.graphic.move(self.xpos,self.ypos)
            time.sleep(0.5)
            
window = GraphWin("window",500,500)

threading.Thread(target=Particle()).start()
threading.Thread(target=Particle()).start()

After different attempts of trying to use threading, I contnued to get two different errors.

This (from the code above):

Exception in thread Thread-1:
Traceback (most recent call last):
Thread-2  File "/usr/lib/python3.11/threading.py", line 1038, in _bootstrap_inner
:
    Traceback (most recent call last):
  File "/usr/lib/python3.11/threading.py", line 1038, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.11/threading.py", line 975, in run
    self._target(*self._args, **self._kwargs)
TypeError: 'Particle' object is not callable
self.run()
  File "/usr/lib/python3.11/threading.py", line 975, in run
    self._target(*self._args, **self._kwargs)
TypeError: 'Particle' object is not callable

and also: "RuntimeError: main thread is not in main loop"

I have tried many suggestions from stack overflow and other websites such as:

note: if you are certain that these methods should work please show me how it should be done and I am new to using the thread module.

thanks for any help!

Upvotes: 0

Views: 100

Answers (2)

cdlane
cdlane

Reputation: 41872

It's rough but I believe it gives you a working starting point:

from queue import Queue
from random import randint
from threading import Thread
from time import sleep
from graphics import *

class Particle:
    def __init__(self):
        xpos = randint(0, 50)
        ypos = randint(0, 50)

        self.graphic = Circle(Point(xpos, ypos), 5)
        self.graphic.draw(window)

    def move(self):
        while True:
            graphic_commands.put((self.graphic.move, randint(1, 5), randint(1, 5)))
            sleep(0.25)  # sleep *this* thread

def process_queue():
    while not graphic_commands.empty():
        graphic, *arguments = graphic_commands.get()
        graphic(*arguments)

    window.after(100, process_queue)

graphic_commands = Queue(1)  # size = ~ number of hardware threads you have - 1

window = GraphWin("window", 500, 500)

process_queue()

Thread(target=Particle().move, daemon=True).start()
Thread(target=Particle().move, daemon=True).start()
Thread(target=Particle().move, daemon=True).start()

window.getMouse()
window.close()

It's based on a similar solution I did for turtle, but with additional twists for graphics.py. The (thread safe) queue is used to make sure all the threads' drawing requests are executed in the main thread for tkinter.

Upvotes: 0

Vinay Davda
Vinay Davda

Reputation: 87

I tried your code,it need some fixes:

  • in threading.Thread target you are passing class instance directly while it accepts methods
  • you can pass Particle.__init__() or Particle().move() as action
  • or you can create object using particle1 = Particle() class and then use it's methods like target=particle1.move()
  • If you use daemon=True it means newly created thread will run in background and main Thread doesn't have to wait till it completes it's process
  • If you call join() on thread's instance like thread1.join(), main thread will wait till that perticular thread completes it's targeted action (daemon=True or False doesn't matter in this case)

Here I am giving you some corrected version of code:

# ... same other code

threading.Thread(target=Particle().__init__()).start()
threading.Thread(target=Particle().__init__()).start()

or

# ... same other code

threading.Thread(target=Particle().move()).start()
threading.Thread(target=Particle().move()).start()

or

# ... same other code

particle1 = Particle()
particle2 = Particle()

threading.Thread(target=particle1.move()).start()
threading.Thread(target=particle2.move()).start()

or

# ... same upper code

particle1 = Particle()
particle2 = Particle()

thread1 = threading.Thread(target=particle1.move()).start()
thread2 = threading.Thread(target=particle2.move()).start()

thread1.join()
thread2.join()

In last snippet, you your window will be opened till your move function completes execution or you close it manually.

If you want to dig deeper in Pytohn Threading, check this guide: https://superfastpython.com/threading-in-python/

Upvotes: 0

Related Questions