FabienPNs blogg.

FabienPNs blogg.

Beskjeden blogg om programmeringsopplevelser.

Qt-traden: enkel, komplett og stabil (med full kilder pa GitHub)

Trad er sikkert et av de mest diskuterte emnene pa Qt-fora. I mellomtiden klarte jeg aldri a finne et enkelt eksempel som beskriver hvordan jeg bare gjor det jeg vil gjore: Kjor en trad for a gjore ting og avbryte det hvis det er nodvendig (for eksempel hvis GUI er stengt). Det er derfor jeg skriver dette innlegget (faktisk, at selv jeg startet denne bloggen & # 8230;). Jeg haper dette vil spare litt tid for de som ikke vil bruke tid pa dokumenter og fora, og ser etter flere stykkene som trengs for a samle denne losningen.

Her er problemet jeg vil lose i dette eksemplet:

Opprett en hovedtrad med GUI Start en annen trad som vil gjore ting (teller 60 sekunder i dette eksemplet, men det kan gjore alt du vil) og lukker riktig nar det er ferdig Nar GUI er stengt, hvis den andre traden kjorer, det vil tvinge det til a avbryte og sa venter pa at det skal avsluttes sa snart som mulig.

Et fullt fungerende prosjekt med alle kilder er tilgjengelig for dette eksempelet pa GitHub: https://github.com/fabienpn/simple-qt-thread-example.

Sa, hvordan fungerer det. Forst trenger vi en klasse som vil gjore arbeidet vi onsker i en egen trad. I dette eksemplet kalles denne klassen Arbeider og vil gjore sitt arbeid i funksjonen doWork (). Funksjonen doWork () venter bare 1 sek og deretter oker en teller. Denne tiln rmingen er den nye & # 8220; gode & # 8221; tiln rming, sammenlignet med den gamle maten a gjore det pa: subclassing QThread. Dette gode innlegget forklarer hvorfor subclassing QThread ikke er relevant lenger siden Qt 4.4: Du gjor det galt og # 8230;

Som vi vil vise motverdien pa GUI, legger vi til en signalverdiChanged () som vil bli sendt ut hver gang verdien endres (!).

Na er den interessante delen som vi trenger litt mer stoff for a gjore denne klassen helt brukbar i en trekkkontekst. Forst trenger vi et ferdig () signal. Dette signalet vil bli sendt ut nar arbeidet er ferdig for a informere traden som den skal stoppe. Da trenger vi en _abort boolean. Nar denne boolske er satt til true, stopper tellerlokken, og doWork () -funksjonen blir ferdig. Sa i utgangspunktet ma hele traden gjore for a stoppe arbeideren er a sette _abort til sann. Vi setter ogsa en _bearbeidende boolsk til falsk pa slutten av funksjonen. Denne boolske vil bli sjekket for a definere om Arbeideren arbeider og dermed, hvis den kan avbrytes. Men ting som er sa enkle som a fa tilgang til en variabel fra begge tradene, kan v re farlige. Det er derfor en QMutex vil bli brukt til a beskytte tilgangene til _abort og _working (jeg er ikke 100% sikker pa at det er nodvendig som lese / skrive-tilgang, kan v re atomisk, men det er alltid en bedre praksis a beskytte variabler mellom trader). Forresten inneholder denne delen av koden en metode for a & # 8220; vente & # 8221; bruker en QTimer. Dette er ogsa et emne som jeg har sett ofte pa Qt-fora.

FunksjonsforesporselenWork () kalles for a informere om at vi vil at traden skal starte. Det vil sette _working til sann og _abort til falsk for a la traden bli avbrutt fra na av.

Funksjonen avbrytes () vil ganske enkelt sjekke om Arbeideren jobber og sett _abort til ekte mens du beskytter den. Denne metoden for tradavbrudd er inspirert av Qt Mandelbrot-eksemplet.

For a sikre at traden ikke blir avbrutt i forste runde, blir _abort og _working satt til false i klassekonstruktoren. Det er ikke nodvendig a beskytte variabler med mutex her fordi Arbeideren fortsatt er i hovedtraden nar den er opprettet, slik at det ikke kan v re samtidig tilgang til variabler.

Og her er hvordan arbeiderklassen ser ut som:

Na er den viktigste GUI-klassen. En av de viktigste reglene i Qt-traden er at alle trader ma stoppes for hovedtraden er slukket. Det er derfor vi ma vente pa at arbeideren traden er ferdig i destructoren. Men traden vant ikke, med mindre telleren er ferdig eller avbrutt. Sa vi ma manuelt avbryte telleren i destructoren, sa vent pa at traden er ferdig. Det er derfor vi trenger pekere til bade, traden og arbeideren i GUI-klassen.

Konstruktoren vil instantiere bade Arbeider og QThread. Deretter flyttes arbeideren til traden. Det vil gi eierskap til denne arbeideren til den andre traden som vil kunne manipulere det fritt. Deretter kobler vi Worker :: valueChanged () signalet til a oppdatere skjermen. Den viktige delen kommer etter. Arbeidsbehandlet () signal fra Arbeider er koblet til start () -funksjonen til traden, noe som betyr at traden vil starte nar requestWork () kalles. Det startede () signalet til traden er koblet til arbeidets doWork () -funksjon, noe som betyr at sa snart traden starter, kalles funksjonen doWork (). Deretter kobler vi det ferdige () signalet til arbeideren til slutten av traden (), slik at traden stopper nar arbeidet er ferdig. Denne forbindelsen ma v re direkte for a sikre at du ikke blir blokkert og venter pa traden for a avslutte (med hendelsen enqueued og aldri behandlet). Denne metoden har blitt inspirert av det gode innlegget fra Maya om dette emnet: Hvordan virkelig, virkelig bruk QThreads; Den fulle forklaringen.

For a starte arbeidet i en annen trad, trenger vi bare a ringe til arbeider-> requestWork (), men for vi ringer det, ma vi sikre at den ikke kjorer. Ventetiden () -funksjonen er det vi trenger for det. Det vil vente til traden er ferdig og deretter tilbake. For a sikre at vi ikke venter hele 60 sekunder og fullforer arbeidet sa snart som mulig, kalder vi abort () pa arbeideren for:

I destructoren avslutter vi traden pa samme mate, og slett deretter objektene.

Og det er det. Med denne metoden er det veldig enkelt a lage & # 8220; en samtale & # 8221; og faste trader kjorer pa en sv rt stabil mate og uten hodepine. Igjen, v r sa snill a ta en titt pa full kildekode pa GitHub og kommentere dette innlegget hvis du har sporsmal eller onsker a forbedre denne metoden. Jeg haper dette vil spare litt tid til noen.

OPPDATERING: I det innlegget forklares det hvordan a ha en trad som loper for alltid og venter pa foresporsler fra ulike metoder. Du kan hodet der hvis du er interessert i dette tilfellet.

EDIT: Takket v re Sandeep, har et problem blitt lost. I forste versjon ble abort () -funksjonen ikke sjekket om arbeidet ble bedt om, men bare hvis traden ble kjort. Det var da mulig a be om abort etter at arbeidsforesporselen var ferdig, men for traden startet, noe som resulterte i at abort ikke ble tatt i betraktning. Derfor har requestWork () blitt innfort, slik at arbeidsforesporsel er gjort i samme trad som abortforesporsel.

Dele denne:

Post navigasjon.

Legg igjen et svar Avbryt svar.

Hva er alle sammen, det er min forste tur a se pa dette nettstedet, og artikkelen er faktisk fruktbar for meg, hold deg oppdatert.

legger ut disse artiklene eller anmeldelsene.

Merci Fabien. Dette var nyttig.

Takk for proven, veldig nyttig.

Fin jobb! Fabien!

Det var en fantastisk artikkel, og hjalp meg med a lose noen problemer i prosjektet mitt. Men jeg fant et problem i koden nedenfor (som jeg praktisk talt var i stand til a oppdage under testing).

Problemet med koden ovenfor er, hvis jeg kaller thread-> start () umiddelbart etterfulgt av arbeider-> avbryte (), sa er det ikke garantert at traden egentlig har startet. Sa din arbeider-> avbryte () ville bli kalt forst og deretter vil traden starte neste, som vil ringe doWork. I dette tilfellet vil traden aldri bli kansellert, fordi i doWork ville vi overstyre _abort = false, som tidligere var _abort = true ved abort () -funksjonen. For a komme seg rundt dette, i stedet for a initialisere _abort = false i doWork, kan vi trygt initialisere _abort = false i Worker’s constructor. I arbeiderkonstruktoren ville vi ikke trenge en mutex, fordi av to grunner:

1) Vi er sikker pa at det ikke er mulig a fa tilgang til _abort, for konstruktoren er ferdig.

2) Vi har ikke kalt moveToThread enna, sa arbeideren er ikke en trad enna, derfor ikke behov for en mutex.

Jeg ville v re takknemlig hvis noen kan kommentere om mine observasjoner er riktige.

Din observasjon er helt riktig. Det er fare for at du avbryter arbeideren mens traden ikke er startet, noe som resulterer i at arbeideren gjor arbeidet mens du ikke vil at det skal gjores. Uansett kan losningen din ikke lose dette problemet helt. Faktisk, hvis du bare angir _abort til falsk i konstruktoren, vil forste gang du ringer abort (), _abort bli satt til sann og det vil ikke bli satt til falsk igjen. Sa traden vant kan ikke lope lenger.

Det du trenger a gjore er a fortelle nar arbeideren kan bli avbrutt. Det kan avbrytes sa snart foresporselen til Arbeider for arbeid er gjort (problemet kommer fra gapet mellom denne foresporselen og det oyeblikket traden faktisk starter). Det er derfor jeg introduserer en ny metode: requestWork () som vil sette en ny variabel _working & lt; / strong til sann og _abort til falsk og deretter be om traden skal starte. abort () trenger bare a sjekke om _working & lt; / strong er sant for du angir _abort til true. Nar begge disse metodene kjorer i hovedtraden, sikrer du at abort () tas i betraktning noyaktig nar det trengs.

Takk for at du har pekt pa dette problemet og hjulpet med a forbedre denne losningen. Jeg oppdaterer innlegget og depotet for a inkludere disse rettighetene.

Jeg hadde fa flere poeng a legge til:

1) Ja, det er mulig a oppdage om abort () ble kalt forst eller traden ble startet forst. Vi kan bare oppdage, men kan ikke kontrollere. Vi kan initialisere dette til _working = true inside & # 8220; doWork & # 8221 ;, slik at vi er sikker pa at traden har startet, og vi kan bruke abort for a tilbakestille _working til false.

2) Samtidig foler jeg meg avbryte () bor bare avbryte en gjeldende trad, eller skal sette tilstanden til en avbrutt tilstand (_abort = true), slik at etter hvert nar traden starter, blir den avbrutt, fordi doWork ville bare komme tilbake. Pa denne maten ville det ikke v re mye komplikasjoner. En annen praktisk fordel med dette er (som jeg ser i prosjektet mitt), kan brukeren for eksempel si 2 knapper 1 – for a starte litt bakgrunnsarbeid og 2 – for a navigere til en annen side. I disse scenariene kan du bare starte traden under knapp 1 klikk, deretter i knapp 2 klikker du bare kan ringe avbryte. I dette tilfellet, uavhengig av trad startet eller ikke, fordi brukeren navigerer til en annen skjerm, avbryter vi bare operasjonen.

Jeg er sikker pa at jeg oversvommer mange kommentarer, noe som kan v re helt uakseptabelt for mange, v r sa snill a slette mine kommentarer / innlegg, hvis du foler deg sa

Faktisk _arbeid er satt til ekte nar du ber om a starte traden (i hovedtraden), ikke nar den faktisk er startet. Pa denne maten er du sikker _abort er satt til sann om traden er startet eller ikke.

Den noyaktige rutinen for a starte traden er:

Arbeider * Arbeider = Ny Arbeider;

QThread * thread = new QThread;

Koble (arbeider, SIGNAL (workRequested ()), trad, SLOT (start ()));

Koble (trad, SIGNAL (startet ()), arbeider, SLOT (doWork ()));

arbeider-& gt; requestWork (); // Som vil sette _working til true, _abort to false og utstede workRequested ().

Pa denne maten, hvis du ringer abort () umiddelbart etter, vil den sette _abort til ekte, uavhengig av om traden faktisk er strengt eller ikke. Hvis du ikke ringer abort () sa er du garantert _abort er feil fordi den er satt av requestWork ().

Hovedideen er at du definerer i samme trad hvis det er mulig a avbryte eller ikke (ved a sette _working i requestWork ()) og hvis du vil avbryte (ved a sette _abort i abort ()). Pa denne maten er du sikker pa at _abort ikke er angitt for du vurderer traden som fungerer.

For det forste var kravet mitt litt annerledes, hvor i tradens omfang vil v re begrenset til 1 lop. Hva jeg mener er, nar DoWork er ferdig, odelegger jeg traden. Mitt prosjekt (krav) hadde ikke et scenario for a gjenbruke traden. Sa jeg hadde ikke problemet med traden som ikke tok opp & # 8220; doWork & # 8221; andre gang.

For det andre kan jeg ikke forsta hvordan jeg introduserer en annen funksjon og tilstandsvariabel, loser timingsproblemet mellom faktisk trad- & gt; start () og worker-> avbryte (). Hvis jeg ikke er feil, kan du tenke pa noe slikt:

Arbeider * Arbeider = Ny Arbeider;

QThread * thread = new QThread;

arbeider-& gt; requestWork (); // Som vil sette arbeidet til ekte og avbryte til falsk.

Men hvis jeg ringer til arbeider-> avbryte () (som vil sjekke om _working == true, og som viser seg a v re sant, setter du abort = true), er det fortsatt ikke garantert at traden har oppgitt, og hvis det er tilfelle, traden er ikke startet, vi kan ikke inneholde / stoppe doWork fra a gjore jobben. Men dette vil motsette ideen om ikke a gjore arbeid, nar traden faktisk ikke har startet.

Arbeider * Arbeider = Ny Arbeider;

QThread * thread = new QThread;

arbeider-& gt; requestWork (); // Som vil sette arbeidet til ekte og avbryte til falsk.

Na, hvis jeg kan arbeide-> avbryte () umiddelbart, er situasjonen ikke annerledes. Det er ikke garantert at traden er startet for abort ().

Jeg endret ditt opprinnelige eksempel i henhold til mine behov, og det samme finnes pa:

Ovennevnte eksempel, handterer alle tilfeller (antar jeg), 1) trad startet og arbeidstaker fullfort arbeid 2) avbryte en lopende trad / arbeidstaker 3) avbryte / rydde opp en enna ikke startet trad.

Du kan bekrefte punkt 3, men kommenterer traden-> start () i koden min og bare ring arbeideren-> avbryte (), den ma ikke ha noen bivirkninger.

Slik jeg ser pa det, nar vi sier trad-> start (), mener jeg egentlig at arbeideren skal begynne, ellers kaller jeg ikke trad-> start. Jeg ville utsette a ringe trad- & gt; start () til jeg er overbevist om a starte. Sa selv om traden-> start (), hvis traden ikke har startet faktisk, og jeg ringer abort, bor det v re sa gjennomsiktig for meg som om traden var startet eller ikke. Til slutt nar traden faktisk begynner sent, kommer den bare ut av doWork, som vi ba om a avbryte. Det jeg mener er, bor abort bare avbryte min aktivitet, uavhengig av traden startet eller ikke.

Beklager hvis jeg hadde forvirret og rystet opp.

Veldig fint! Som du sa, ga du et perfekt eksempel, med alt vi ville se pa ulike koblinger & # 8230; og du koblet dem til og med her!

Gratulerer, og tusen takk for deg hjelp!

Fungerer denne implementeringen med qt 4.7? Og hvis ikke, hva er problemet? Jeg er ny til qt, og jeg ma bruke 4.7 for et innebygd system. Github-siden sier at den bare fungerer pa & gt; qt4.8 og jeg vil gjerne vite hva som er forskjellen & # 8230; Flott arbeid, forresten!

Hei Julio! Grunnen til at jeg sa det fungerer pa Qt4.8 er fordi jeg testet det pa denne versjonen som det var den siste 4.x da jeg skrev artikkelen. Jeg antar at det skulle fungere pa Qt4.7, men aldri testet det.

Disse forklaringene var veldig nyttige, takk!

Jeg oversatte dette eksemplet i python ved hjelp av PyQt og sett det her:

Jeg sa ogsa en skrivefeil i den andre koden:

& # 8220; void Worker :: abort () & # 8221; bor v re & # 8220; ugyldig Arbeider :: requestWork () & # 8221;

Jeg heter Fabien Pierre-Nicolas. Jeg er en programvareutvikler som for tiden jobber med ulike prosjekter fra innebygde systemer til webutvikling. Programmering er min jobb og min hobby, og jeg startet denne bloggen for a dele noen av mine ydmyke opplevelser. Oh, og jeg er fransk, og som noen gode franskmenn er engelsken min forferdelig. Sa v r sa snill a tilgi meg for det.