Reactie plaatsen 
 
Waardering:
  • 0 stemmen - gemiddelde waardering is 0
  • 1
  • 2
  • 3
  • 4
  • 5
[C#-FAQ]De oplossing voor: Hoe kan ik m'n GUI deblokkeren?
Auteur Bericht
Elco Offline
Ep2 Volunteer
****

Berichten: 1.237
Lid sinds: 04-2006
Reputatie: 29
Bericht: #1
[C#-FAQ]De oplossing voor: Hoe kan ik m'n GUI deblokkeren?
FAQ: Hoe kan ik m'n GUI deblokkeren?

Deze vraag komt de laatste tijd geregeld terug op het forum: waarom blokkeert mijn programma en komt er niets meer in m'n controls? Tijd dus voor een FAQ-tutorial die het probleem en de oplossing uitlegt :)

Het probleem

In deze tutorial gaan we een vaak voorkomend probleem behandelen: het freezen van de UI bij het uitvoeren van een taak. Zo'n taak kan bestaan uit een hele reeks intensieve berekeningen, maar ook bijvoorbeeld het wachten op een binnenkomende verbinding (blocking IO) in een client-server applicatie, waardoor je niets meer kan doen.

Om het probleem duidelijker te illustreren maken we volgend programma: een programma bevat een form met 2 buttons (“Start� en “Stop�), een richttextbox en een progressbar (progressbar ingesteld met step op 1) Voor de eenvoud en om het eenvoudig zelf te doen heb ik gewoon de standaard naamwaarden gebruikt voor de componenten. Button1 is de start knop en Button2 is de stopknop.
De code in het formulier is heel eenvoudig en ziet er als volgt uit:
Code:
private Boolean doCalculation = true;
        public Form1()
        {
            InitializeComponent();
        }

        //Start button
        private void button1_Click(object sender, EventArgs e)
        {
            doCalculation = true;
            doCalculations();
        }

        private void doCalculations() {
            int number = 0;
            int result = 0;

            for (; doCalculation; number++) {
                result = number * number;
                richTextBox1.AppendText("Status: " + number % 100);
                progressBar1.Value = number % 100;            
            }
        }

        //Stop button
        private void button2_Click(object sender, EventArgs e)
        {
            doCalculation = false;
        }
Bij het klikken op de startknop gaat het programma aan het rekenen. Dit is maar een arbitrair voorbeeld wat absoluut niets zinnigs doet, het is enkel om de processor bezig te houden :) Dit doet het zolang de doCalculation-boolean true is. Deze kan false gemaakt worden door op de stop knop te drukken.

Als je dit programma echter uitvoert, en je wil op de stopknop drukken, dan merk je dat die stopknop niet reageert! Dit komt omdat alle code in dezelfde thread wordt uitgevoerd. Hierdoor kan er binnen de thread maar 1 stuk code tegelijkertijd worden uitgevoerd. Aangezien het programma volop bezig is in de for lus om de berekeningen te maken, krijgt het programma gewoon geen kans om de code van de stopknop uit te voeren... (Dit is analoog bij programma's die staan te wachten op in te lezen gegevens uit bestanden, sockets, ... ) Ook als je het programma wil afsluiten krijg je een hangend programma en wordt zelfs je programma niet meer hertekend:
    Hier moeten we dus duidelijk een oplossing voor vinden, aangezien dit absoluut niet goed overkomt bij de gebruiker van je programma.

De oplossing

Wat we nu moeten doen is de code in de berekeningen in een aparte thread uitvoeren. Dat kan zeer eenvoudig door volgende code te gebruiken in de startknop:
Code:
private void button1_Click(object sender, EventArgs e)
        {
               doCalculation = true;
      Thread myFirstThread = new Thread(new ThreadStart(doCalculations));
      myFirstThread.Start();
   }
Op deze manier wordt een nieuwe thread gestart die als beginpunt de methode doCalculations heeft.

MAAR: als we dit uitvoeren zien we een foutmelding:
Citaat:“Cross-thread operation not valid: Control 'richTextBox1' accessed from a thread other than the thread it was created on.�
Dit komt omdat je in C# geen toegang hebt tot user controls die in een andere thread werden aangemaakt... Aangezien de controls op het formulier gemaakt werden door de code die in de main-thread loopt kunnen we er van in onze eigen thread niet meer aan om de instelling te wijzigen. Maar ook hier hebben we een oplossing voor :) We maken gebruik van een delegate.

We voorzien een methode in het formulier die voor ons de instellingen zal doen:
Code:
private void setControls(int number) {
            richTextBox1.AppendText("Status: " + number % 100);
            progressBar1.Value = number % 100;
        }
We kunnen nu nog niet gewoon volgende code uitvoeren in doCalculations, want dan blijft het probleem: we benaderen nog steeds de UI vanuit onze eigen thread en niet vanuit de mainthread waarin de componenten gemaakt werden.

Wat er moet gebeuren is een delegate aanmaken in het formulier:
Code:
public delegate void SetControlsCallback(int number);
Die dezelfde argumenten neemt als onze setControls methode. Deze zal dienst doen om die setControls methode te laten uitvoeren.

Voor code in doCalculations krijgen we nu dit:
Code:
private void doCalculations() {
            int number = 0;
            int result = 0;

            for (; doCalculation; number++) {
                result = number * number;
                Object[] arguments = new Object[1];
                arguments[0] = number;
                this.Invoke(new SetControlsCallback(this.setControls),arguments);            
            }
        }
Waar het hier om draait is deze code:
Code:
Object[] arguments = new Object[1];
                arguments[0] = number;
                this.Invoke(new SetControlsCallback(this.setControls),arguments);
Zoals te zien is maken we hier een nieuwe callback-instantie aan. Deze verwijst naar de setControls-methode. De this wijst erop dat die verwijst naar de methode in het object dat op dat moment aan het uitvoeren is (dus ons formulier).
Elke UI-component heeft een methode invoke, die kan gebruikt worden om code uit te voeren in de thread waarin die specifieke component werd aangemaakt. Dat geldt dus ook voor een formulier. Door
Code:
this.Invoke(new SetControlsCallback(this.setControls),arguments);
uit te voeren, zeggen we dus eigenlijk: voer (invoke) in de thread waarin dit (this) formulier werd aangemaakt, de code “setControls� van dit object (this) uit, met de argumenten (arguments) zoals opgegeven.

Het eindresultaat

De volledige code ziet er

als volgt uit


private Boolean doCalculation = true;

public delegate void SetControlsCallback(int number);

public Form1()
{
InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)
{
doCalculation = true;
Thread myFirstThread = new Thread(new ThreadStart(doCalculations));
myFirstThread.Start();
}

private void doCalculations() {
int number = 0;
int result = 0;

for (; doCalculation; number++) {
result = number * number;
Object[] arguments = new Object[1];
arguments[0] = number;
this.Invoke(new SetControlsCallback(this.setControls),arguments);
}
}

private void setControls(int number) {
richTextBox1.AppendText("Status: " + number % 100);
progressBar1.Value = number % 100;
}

private void button2_Click(object sender, EventArgs e)
{
doCalculation = false;
}


Als we nu ons programma uitvoeren merken we direct dat we de stopknop nu wel kunnen gebruiken! :)
Er is nog een andere manier mogelijk om dit te doen: met een BackgroundWorker-control, maar dat is voor een andere tutorial ;)

Verdere vragen, opmerkingen, ... zijn uiteraard steeds welkom :)

C#.Net Developer
13-11-2006 13:49:04
De website van deze gebruiker bezoeken Alle berichten van deze gebruiker zoeken Reageren op dit bericht
PeterE Offline
Member
***

Berichten: 177
Lid sinds: 04-2006
Reputatie: 1
Bericht: #2
RE: [C#-FAQ]De oplossing voor: Hoe kan ik m'n GUI deblokkeren?
Whiee! Mooie toevoeging :)! ++
13-11-2006 14:23:08
De website van deze gebruiker bezoeken Alle berichten van deze gebruiker zoeken Reageren op dit bericht
Jasper Offline
Ep2 Admin
******

Berichten: 15.056
Lid sinds: 04-2006
Reputatie: 223
Bericht: #3
RE: [C#-FAQ]De oplossing voor: Hoe kan ik m'n GUI deblokkeren?
Altijd leuk als er een tut/FAQ wordt geschreven aan de hand van vragen van members! Thanks!

Om de zeven minuten denkt de vrouw dat de man alleen maar aan seks denkt.
14-11-2006 01:17:44
De website van deze gebruiker bezoeken Alle berichten van deze gebruiker zoeken Reageren op dit bericht
MartijnD Offline
Member
***

Berichten: 62
Lid sinds: 10-2006
Reputatie: 0
Bericht: #4
RE: [C#-FAQ]De oplossing voor: Hoe kan ik m'n GUI deblokkeren?
Erg net artikel, komt wel net te laat want gisteren is m'n probleem met het form wat gefreezed raakt opgelost. Zou graag als vervolg ook een artikel zien waarin uitleg gegeven wordt over background workers, aangezien dit mij nog niet helemaal duidelijk is..

Hack Jelle's Game! http://rapidshare.com/files/43291136/Spa...k.rar.html
(Dit bericht is het laatst bewerkt op 14-11-2006 om 09:48:18 door MartijnD.)
14-11-2006 09:47:51
Alle berichten van deze gebruiker zoeken Reageren op dit bericht
Elco Offline
Ep2 Volunteer
****

Berichten: 1.237
Lid sinds: 04-2006
Reputatie: 29
Bericht: #5
RE: [C#-FAQ]De oplossing voor: Hoe kan ik m'n GUI deblokkeren?
Bedankt allemaal al voor de positieve reacties :)
MartijnD schreef:Zou graag als vervolg ook een artikel zien waarin uitleg gegeven wordt over background workers, aangezien dit mij nog niet helemaal duidelijk is..
Dat komt eraan, vandaag of morgen zal ik het posten! :)

C#.Net Developer
14-11-2006 09:58:29
De website van deze gebruiker bezoeken Alle berichten van deze gebruiker zoeken Reageren op dit bericht
Jasper Offline
Ep2 Admin
******

Berichten: 15.056
Lid sinds: 04-2006
Reputatie: 223
Bericht: #6
RE: [C#-FAQ]De oplossing voor: Hoe kan ik m'n GUI deblokkeren?
Elco schreef:Dat komt eraan, vandaag of morgen zal ik het posten! :)
Wat een lauw-toffe-tranga-flexie* member ben je toch!

*lol, een toffe member dus :D

Om de zeven minuten denkt de vrouw dat de man alleen maar aan seks denkt.
14-11-2006 22:48:55
De website van deze gebruiker bezoeken Alle berichten van deze gebruiker zoeken Reageren op dit bericht
Elco Offline
Ep2 Volunteer
****

Berichten: 1.237
Lid sinds: 04-2006
Reputatie: 29
Bericht: #7
RE: [C#-FAQ]De oplossing voor: Hoe kan ik m'n GUI deblokkeren?
Jasper schreef:Wat een lauw-toffe-tranga-flexie* member ben je toch!
*lol, een toffe member dus :D
Hehe, thanks :D

C#.Net Developer
(Dit bericht is het laatst bewerkt op 14-11-2006 om 23:13:24 door Elco.)
14-11-2006 23:12:59
De website van deze gebruiker bezoeken Alle berichten van deze gebruiker zoeken Reageren op dit bericht
MartijnD Offline
Member
***

Berichten: 62
Lid sinds: 10-2006
Reputatie: 0
Bericht: #8
RE: [C#-FAQ]De oplossing voor: Hoe kan ik m'n GUI deblokkeren?
[Skip] Het lukt me nog steeds niet, zou je misschien een werkend project toe kunnen voegen? Dan kan ik precies vergelijken wat jij anders hebt.

Hack Jelle's Game! http://rapidshare.com/files/43291136/Spa...k.rar.html
(Dit bericht is het laatst bewerkt op 21-06-2007 om 11:44:36 door MartijnD.)
17-05-2007 10:42:56
Alle berichten van deze gebruiker zoeken Reageren op dit bericht
Reactie plaatsen 


Ga naar locatie:


Contact opnemen | Ep2 | Naar boven | Naar inhoud | Archiefmodus | RSS-syndicatie