riko
> "semi-abandonware" (cosa che per inciso non mi torna, PySide sembrava abbastanza mantenuto)
Sì, "mantenuto" è la parola chiave qui. Non c'è molta innovazione.
riko
> sei confuso su cosa sia FRP. O meglio... tu lo stai leggendo come:
> Functional reactive programming
Uhm, no, stavo solo argomentando... nei fatti mi sembra che frp sia una roba che si fa più spesso / esistono più soluzioni per linguaggi funzionali... Elm, Haskel... Ok c'è Sodium per Java/c++... ma non c'è per python... e men che meno per wxPython / gui varie.
Però, se accettiamo che il *principio* di frp possa essere implementato alla buona in termini OOP come un buon sistema di observer gestito da un main loop, allora questo è sicuramente possibile farlo nei gui framework. Le qt ce l'hanno più o meno nativo (signal/slot), per le wx è più complicato ma non tanto.
riko
> Magari inizi cosi'. Poi cominci a pensare che anche quell'altra operazione e' potenzialmente lenta e/o bloccante.
> E via un altro thread. E poi arriva quella che non e' puro output, ma bisogna anche prenderne il valore.
> E poi arrivano piu' thread che vogliono lavorare sullo stesso pezzo di stato. Etc etc etc.
Ah ma attenzione. Non sto dicendo che i thread sono una buona cosa. E non sto dicendo che i gui toolkit hanno un effetto magico per cui neutralizzano i problemi dei thread. I thread restano thread. E' sempre possibile spararsi sui piedi. Ma quel che voglio dire è che i gui toolkit ti suggeriscono gentilmente come puntare la pistola nella direzione non pericolosa.
bebo:
> Perche' vengono "altamente sconsigliati" dappertutto i thread
> accetto volentieri anche link ad articoli chiarificatori ;-)
Guarda, il discorso di Pike linkato da Riko non fa una grinza. Un altro link che mi resta sempre impigliato nei bookmark è questo
https://glyph.twistedmatrix.com/2014/02/unyielding.html dell'uomo dietro a Twisted... è un po' vecchiotto, di quando asyncio ancora si chiamava tulip, ma è piuttosto didattico e mi piace sempre molto.
A quanto ne so, il più vecchio documento che sconsiglia i thread è questo
http://web.stanford.edu/~ouster/cgi-bin/papers/threads.pdf. Forse Riko ne conosce di più vecchi, ma questo è stato seminale, è ancora oggi molto citato.
A me piace molto per una ragione che c'entra con questo discorso. Lui, al posto dei thread consiglia in sostanza di usare i callback gestiti da un mainloop. Ora, noi magari oggi possiamo storcere il naso, e comunque abbiamo delle opzioni in più. Ma non è un caso che proprio i gui toolkit, che sono più o meno coevi di quel documento, hanno abbracciato esattamente questa struttura: rigorosamente single-threaded, con un motore a eventi e callback gestiti da un mainloop.
All'epoca, questo fatto che i thread fossero un male era particolarmente evidente proprio nei gui toolkit. Se ci pensi, un gui toolkit sta esattamente al centro di una tempesta perfetta fatta di tre possibili sorgenti di guai:
- primo, deve gestire i segnali che vengono dall'alto, dall'utente: se tu scrivi in una casella di testo, il gui toolkit deve ridisegnare la casella a ogni nuova lettera che inserisci
- secondo, i segnali che vengono dal mezzo, da se stesso: tu magari non tocchi la casella di testo, ma mettiamo che invece apri un menu, e la tendina del menu copre la casella, e poi esci da menu e la casella è di nuovo visibile. Il gui toolkit deve sapere che deve ridisegnare la casella;
- terzo, deve gestire i segnali che gli vengono dal basso, dal sistema operativo: se l'utente trascina una finestra "estranea" sopra quella del tuo programma e copre temporaneamente la casella di testo, anche in quel caso il gui toolkit deve saperlo.
Ora, se provi a risolvere un problema del genere coi thread, sei morto: questo è proprio il genere di scenario in cui i thread combinano disastri. Di che scenario stiamo parlando? Come dice RIko...
riko
> programmazione sincrono a memoria condivisa. Questo e' il male.
Proprio così. E infatti. Nessun gui toolkit usa una architettura a thread. Su questo non ci piove, e siamo d'accordo.
Ma proprio perché i gui toolkit hanno abbracciato una struttura diversa e sicura, rendono un pochino più agevole la vita a *te* se vuoi aggiungere un thread al mix per conto tuo. L'unica regola veramente fondamentale che devi ricordare è: mai toccare la gui "direttamente" da un thread secondario. Che cosa vuol dire "direttamente"? Che se hai una casella di testo e fai qualcosa come "casella.SetValue("hello")" da un thread secondario, la gui va in crash, non si scampa. Ma se invece di fare una cosa del genere, ti rivolgi gentilmente al mailoop e gli chiedi di schedulare il fatto che vuoi settare la casella di testo, allora sei di nuovo sul terreno solido. Basta rivolgersi sempre al mainloop. Le chiamate al mainloop sono sempre thread-safe.
Quindi, riassumendo, in un gui toolkit puoi fare queste cose coi thread in modo *sicuro*:
- usare un thread per un compito "locale" e "solitario", senza contatti con la gui: esempio, preparare un lavoro di stampa in background, senza bloccare la gui durante il calcolo.
- come sopra, ma aggiornare (alla fine, o periodicamete) un elemento della gui: esempio, un download lungo che periodicamente aggiorna un gauge con la percentuale di lavoro completata: nessun problema, basta non aggiornare il gauge direttamente ma chiedere al mainloop di schedulare.
- come sopra, ma scatenare la fantasia: più thread che aggiornano in modo concorrente lo stesso elemento della gui, e che magari scambiano anche dati tra di loro... Di nuovo, nessun problema: basta chiedere sempre al mainloop di fare da intermediario. OPPURE, puoi abbandonare il mainloop e usare le tue primitive di sincronizzazione, se sei sicuro... ma a questo punto il gui framework non ti sostiene più, e non venire a lamentarti se poi succedono disastri. Posto comunque sempre che, se vuoi interagire con la gui, allora l'intermediazione del mainloop è obbligatoria.
Questo è l'hello word dei thread in wxPython:
[codice]
import wx, threading, time
class Worker(threading.Thread):
def __init__(self, target_box):
threading.Thread.__init__(self)
self._target_box = target_box
def run(self):
for i in range(10):
# self._target_box.SetValue(str(i)) # questo non va bene!
wx.CallAfter(self._target_box.SetValue, str(i)) # ma questo si'
time.sleep(2)
class Main(wx.Frame):
def __init__(self, *a, **k):
wx.Frame.__init__(self, *a, **k)
p = wx.Panel(self)
self.box = wx.TextCtrl(p, pos=(10, 10))
b = wx.Button(p, -1, 'start', pos=(10, 50))
b.Bind(wx.EVT_BUTTON, self.on_b)
def on_b(self, evt):
worker = Worker(self.box)
worker.start()
app = wx.App()
Main(None).Show()
app.MainLoop()
[/codice]
Qui la magia è tutta in
wx.CallAfter, che implicitamente crea un evento e lo posta in coda nel mainloop. Questo è praticamente tutta la sintassi in più che bisogna sapere per usare un thread secondario che interferisce con la gui. Ma si può usare anche wx.CallAfter come sistema generale di comunicazione fra i thread, indipendentemente dalle azioni sulla gui, volendo.
Non è il rimedio di tutti i mali dei thread, ma è comunque un bel sollievo.