Timer error in multithreading

Hello !

I have the following problem :

Main.py:

import threading
import FilePlot

while True:
    word = input()
    if word == "plot":
        threading.Thread(target=FilePlot.runPlot).start()

FilePlot.py:

import wx
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wx import NavigationToolbar2Wx as NavigationToolbar
import threading
from time import sleep

class PlotWindow(wx.Frame):
    def __init__(self, *args, **kwds):
        kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)
        self.SetSize((657, 548))
        
        # Menu Bar
        self.PlotMenuBar = wx.MenuBar()
        wxglade_tmp_menu = wx.Menu()
        self.PlotMenuBar.loadDataMenu = wxglade_tmp_menu.Append(wx.ID_ANY, "Load data to plot", "")
        self.PlotMenuBar.saveImageMenu = wxglade_tmp_menu.Append(wx.ID_ANY, "Save plot image to file", "")
        self.PlotMenuBar.Append(wxglade_tmp_menu, "File")
        wxglade_tmp_menu = wx.Menu()
        self.PlotMenuBar.logXmenu = wxglade_tmp_menu.Append(wx.ID_ANY, "Log(x)", "", wx.ITEM_CHECK)
        self.PlotMenuBar.logYmenu = wxglade_tmp_menu.Append(wx.ID_ANY, "Log(y)", "", wx.ITEM_CHECK)
        self.PlotMenuBar.absXmenu = wxglade_tmp_menu.Append(wx.ID_ANY, "Abs(x)", "", wx.ITEM_CHECK)
        self.PlotMenuBar.absYmenu = wxglade_tmp_menu.Append(wx.ID_ANY, "Abs(y)", "", wx.ITEM_CHECK)
        wxglade_tmp_menu.AppendSeparator()
        self.PlotMenuBar.keepPlotMenu = wxglade_tmp_menu.Append(wx.ID_ANY, "Keep last measurement on plot", "", wx.ITEM_CHECK)
        self.PlotMenuBar.clearLastMenu = wxglade_tmp_menu.Append(wx.ID_ANY, "Clear last loaded plot", "")
        self.PlotMenuBar.Append(wxglade_tmp_menu, "Plot")
        self.SetMenuBar(self.PlotMenuBar)
        # Menu Bar end

        self.__set_properties()
        self.__do_layout()
        

    def __set_properties(self):
        self.SetTitle("PlotWindow")
        self.SetBackgroundColour(wx.Colour(104, 104, 104))

        self.Bind(wx.EVT_CLOSE, self.OnExit)
        

    def __do_layout(self):
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        secondSizer = wx.BoxSizer(wx.VERTICAL)
        labelSizer = wx.BoxSizer(wx.HORIZONTAL)

        self.plot_panel = MatplotPanel(self)
        
        secondSizer.Add(self.plot_panel, 8, wx.ALL | wx.EXPAND, 0)
        
        
        
        self.cursorCoordLabel = wx.StaticText(self, wx.ID_ANY, "x: None, y: None")
        #self.cursorCoordLabel.SetBackgroundColour(wx.Colour(221, 221, 221))
        self.cursorCoordLabel.SetForegroundColour(wx.Colour(255, 255, 255))
        self.cursorCoordLabel.SetFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, ""))
        self.cursorCoordLabel.SetToolTip("Cursor coordinates")
        labelSizer.Add(self.cursorCoordLabel, 1, 0, 0)
        self.distPlotLabel = wx.StaticText(self, wx.ID_ANY, "dx: None, dy: None")
        #self.distPlotLabel.SetBackgroundColour(wx.Colour(50, 153, 204))
        self.distPlotLabel.SetForegroundColour(wx.Colour(255, 255, 255))
        self.distPlotLabel.SetFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, ""))
        self.distPlotLabel.SetToolTip("Distances for x and y. Left click on plot for the first point and Right click on plot for the second point")
        labelSizer.Add(self.distPlotLabel, 1, 0, 0)
        secondSizer.Add(labelSizer, 0, wx.ALL | wx.EXPAND, 0)
        fileNameLabel = wx.StaticText(self, wx.ID_ANY, "File name: None")
        fileNameLabel.SetBackgroundColour(wx.Colour(94, 94, 94))
        fileNameLabel.SetForegroundColour(wx.Colour(254, 254, 254))
        fileNameLabel.SetFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, ""))
        fileNameLabel.SetToolTip("This represent the name of the file used to store measurement data")
        secondSizer.Add(fileNameLabel, 0, wx.EXPAND, 0)
        mainSizer.Add(secondSizer, 1, wx.EXPAND, 0)
        self.SetSizer(mainSizer)
        self.Layout()
        self.Centre()

        #animate plot
        self.xi = []
        self.yi = []
        threading.Thread(target=self.startPlot).start()
        self.plot_panel.canvas.draw()

        self.redraw_timer = wx.Timer(self) # foloseste timer pentru a updata plot-ul
        self.Bind(wx.EVT_TIMER, self.on_redraw_timer, self.redraw_timer)# executa functia on_redraw_timer       
        self.redraw_timer.Start(100) # 100 ms

    def on_redraw_timer(self,event):

        self.plot_panel

        self.plot_panel.plot_data.set_xdata(self.xi)
        self.plot_panel.plot_data.set_ydata(self.yi)


        
        self.plot_panel.axes.relim()
        self.plot_panel.axes.autoscale_view()
        self.plot_panel.canvas.draw()

    def startPlot(self):
        for i in range(20):
            self.xi.append(i)
            self.yi.append(i*i)
            sleep(1)

    def OnExit(self, event):
        warning = wx.MessageBox("Are you sure you want to exit ?", \
                                        'Atention', wx.YES_NO | wx.ICON_WARNING)
        if warning == wx.YES:
            self.redraw_timer.Stop()
            self.Destroy()
        
        


class MatplotPanel(wx.Panel):
    
    def __init__(self, parent):
        wx.Panel.__init__(self, parent,-1,size=(657, 548))

        self.figure = Figure(facecolor='white',dpi = 95)
        self.axes = self.figure.add_subplot(111,facecolor='white')
        self.figure.subplots_adjust(left=0.08, right=0.95, top=0.96, bottom=0.1)

        
        self.canvas = FigureCanvas(self,-1,self.figure)

        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.canvas, -1, wx.EXPAND| wx.ALL)
        self.SetSizer(self.sizer)

        self.plot_data, = self.axes.plot([],[],"-o",color="coral",markersize=4, markeredgecolor='olive')


class MyApp(wx.App):
    def OnInit(self):
        self.frame = PlotWindow(None, wx.ID_ANY, "")
        self.SetTopWindow(self.frame)
        self.frame.Show()
        return True


def runPlot():
    app = MyApp(0)
    app.MainLoop()

If I run the Main.py and I write “plot” in the console it will open the plot window and works very well, but if I close the plot window and write again “plot” in the console the following error will print in the console:

Traceback (most recent call last):
  File "./Lib\plot.py", line 172, in OnInit
    self.frame = PlotWindow(None, wx.ID_ANY, "")
  File "./Lib\plot.py", line 33, in __init__
    self.__do_layout()
  File "./Lib\plot.py", line 89, in __do_layout
    self.redraw_timer.Start(100) # 100 ms
wx._core.wxAssertionError: C++ assertion "wxThread::IsMain()" failed at ..\..\src\common\timerimpl.cpp(60) in wxTimerImpl::Start(): timer can only be started from the main thread

I don’t understand what is the problem. Is there a solution for this problem? Thank you!

Yes, you are running wxPython GUI code from a secondary thread (ie, not
the main one). You can’t do that. All wxPython GUI code, to include
timers, should be run from the main thread, as this assertion is telling
you. :slight_smile:

Scott

Well, I am a newbie and I don’t have any idea how to do that. The plot window was generated by wxglade.

Also, you’re creating multiple wx.App instances.

Have a look at the matplotlib examples supplied with wxGlade and the related documentation:

The main program is “matplotlib_example.py”

For clarification, the thread that creates the wx.App is the one considered to be the main GUI thread. Creating a new wx.App on a new thread will not work, because the first one is still the GUI thread, even if it has been stopped or destroyed. Finally, even with one GUI thread with one wx.App, you can not do anything on a non-GUI thread that causes creation, manipulation or destruction of a GUI object.

On Windows and Linux it will usually work if that main GUI thread is not what the OS considers to be the main thread. On macOS it will not.

Any idea or hint were should I start from?

This is the main program:

    app = MyApp(0)
    app.MainLoop()

Execute it as main program and you’re done.