Springen naar inhoud


- - - - -
VS 2008

Data Verzenden Naar Een Backgroundworker

VS2015

  • Log in a.u.b. om te beantwoorden
Er zijn 6 reacties in dit onderwerp

#1 Coldrestart

Coldrestart

    Starting Developer

  • Leden
  • 4 berichten
    Laatst bezocht 14 feb 2020 22:28

Geplaatst op 03 februari 2020 - 20:36

Hallo,

Eerst en vooral, ik ben een hobbyist, dus mijn programmeerkennis is niet zo goed (en ik probeer die te verbeteren ;-)).

Ik zal eerst even de structuur van mijn applicatie uitleggen en wat juist de bedoeling is.
Ik heb momenteel een applicatie die data over het netwerk gaat halen bij clients.
Deze transactie wordt uitgevoerd in een lus, dus poll client 1, poll client 2, poll client 3, poll client 1 enz....
De transactie zelf kan soms lang duren en timeouts geven, vandaar dat ik met de backgroundworker werk.

Hoe mijn applicatie momenteel werkt:
Een timer start de backgroundworker, die voert de poll uit en nadien verhogen we de teller die we bijhouden om de volgende client te behandelen.

Hoe ik graag mijn applicatie zou willen laten werken:
De "lus" zou ik graag volledig in de backgroundworker willen steken.
Via het event "reportprogress" kan ik al data tijdens het verloop vanuit de backgroundworker, naar de UI thread sturen.
Echter wil ik graag "at runtime", kunnen beslissen welke clients nog gepolled moeten worden.
Stel, ik wil dat client 2 niet meer wordt uitgevoerd.
Op dat moment zou ik die "update" naar de backgroundworker moeten sturen.
Hoe kan ik dat doen?

Ik heb al veel gelezen over threads, thread synchronization, backgroundworker, threadpool (blijkbaar nog iets anders dan een "gewone thread??")
Echter zijn veel voorbeelden in het engels en begrijp ik het niet goed.
Soms spreken ze over delegate, dan weer over sync en synclock.... elk voorbeeld is wel op een andere manier uitgevoerd...
Ik heb wel enkel nederlandstalige boeken, maar daar zijn de voorbeelden niet altijd compleet.
Momenteel snap ik wat een delegate doet, verwijzen naar een functie, maar hoe ik daarmee data (threadsafe) in de backgroundworker kan krijgen, snap ik niet.

Daarnaast dacht ik elke client in een apparte thread (of backgroundworker) te steken, maar blijkbaar bij véél clients (>100) zou blijkbaar je pc daar moeite mee beginnen krijgen.
Opzich is de polling snelheid van mijn applicatie niet kritisch, dus ik denk dat het in een lus de clients opvragen nog het eenvoudigste en stabielste zal zijn.

In de tutorial sectie van deze website heb ik de uitleg van de backgroundworker gevonden, echter spreekt men dat er een "advanced" versie zou volgen met "invoke" en "delegates", maar die heb ik niet gevonden.

Alvast bedankt voor jullie hulp.

Groeten,
Coldrestart.

#2 Supervos

Supervos

    Guru Developer

  • Leden
  • PipPipPipPipPipPip
  • 1409 berichten
    Laatst bezocht vandaag, 13:11
  • LocatieBrugge

Geplaatst op 04 februari 2020 - 00:27

Dag Coldrestart,

Het werken met threads is inderdaad complex en vereist een andere denkwijze om mogelijke fouten te voorkomen.

Misschien een paar van de termen die je aanhaalt wat proberen te verduidelijken:
* Threads: Vanuit het Engels: een draad: je kan je code zien als een lijst van instructies die op een bepaalde draad worden uitgevoerd (een soort parelsnoer). Op bepaalde punten kan de draad zich opsplitsen en instructies naast elkaar uitvoeren, die draden kunnen zich terug samenvoegen of gewoon op niets uitlopen. Er zijn talloze andere manieren om dit te bekijken. Een voorbeeld dat ik vaak aanhaal is dat je een kamer hebt met verschillende personen. Iedere persoon is 1 thread en kan maar met 1 iets tegelijk bezig zijn;
* Thread synchronization: dit is zorgen dat 2 threads zich op elkaar afstemmen, in het voorbeeld met de kamer met personen, je zorgt dat bepaalde informatie over alle personen (die de informatie nodig hebben) wordt verspreid;
* BackgroundWorker: dit is een speciale klasse die gebruikt wordt in formulieren om iets op de achtergrond te kunnen uitvoeren en dat deze op bepaalde momenten met je formulier kan communiceren;
* Threadpool: In het voorbeeld van de personen: om een nieuwe persoon aan te nemen (thread starten) en te ontslaan (thread heeft het einde bereikt) is complex en traag. Een oplossing hiervoor is om een aantal personen op stand-by te hebben. Van zodra er een nieuwe taak binnen komt zal dit aan 1 van de vrije personen worden gegeven, is die klaar met zijn taak zal hij terug keren naar een wachtruimte tot er een nieuwe taak binnen komt;
* Delegate: Dit is een manier om het aanroepen van een functie in een variabele te kunnen bewaren zodat je die later kan aanroepen of kan doorgeven naar een ander stuk code. In VB.NET kan je dit verkrijgen met de ´AddressOf´;
* sync/synclock: ´sync´ (C#) en ´synclock´ (VB.NET) zijn hetzelfde, enkel in een andere taal. Hiermee zeg je dat een bepaald stuk code maar door 1 persoon tegelijk mag worden uitgevoerd. Als je code aan een synclock komt zal deze nakijken of iemand anders al een lock heeft. Als dit zo is zal je code wachten tot die andere code het einde van de synclock heeft bereikt. Als nog niemand een lock heeft zal je code de lock nemen en bij het einde van de synclock de lock vrijgeven (zodat eventuele andere threads die aan het wachten zijn deze lock kunnen overnemen en verder werken);
* atomair: Dit betekent dat je instructie als 1 geheel wordt uitgevoerd, het is niet opdeelbaar in kleinere deeltjes. Voorbeelden hiervan is het nemen van een synclock of 32 bit uit het geheugen lezen of wegschrijven. Let op het gebruik van 32 bit, een double (kommagetal) is 64 bit. Als 2 threads tegelijk een double in het geheugen wegschrijven doen ze dat allebei in 2 delen, de ene kan beginnen met de eerste 32 bit en dan de andere 32 bit, de andere thread kan eerst de laatste 32 bit wegschrijven en dan pas de eerste 32 bit. Hierdoor kan het komen dat berekeningen met doubles heel vreemde waardes kunnen uitkomen. Hiervoor moet je dus het verschil kennen tussen een 'structure' en een 'class'. Een class kan heel veel informatie bevatten maar de verwijzing naar een class bestaat altijd maar uit 32 bit. Als je een variabele naar een andere class laat verwijzen ( MyObj.A = b ) is dit een atomaire instructie. Een structure (datetime, int, double, bool, ...) kunnen groter of kleiner zijn dan 32 bit, deze die groter zijn zullen niet atomair zijn en kunnen tot vreemde resultaten leiden.


Locks zijn heel belangrijk om fouten te kunnen vermijden. Stel je een klas voor met 2 tafels (2 personen/threads) en een bord (je geheugen). Als je aan beide personen vraagt om het getal dat op het bord staat te verhogen met 1 zullen beide hetzelfde doen: ze zullen de waarde die op het bord staat overnemen op hun eigen blad (cpu cache), 1 er bij optellen en dan die waarde op het bord gaan schrijven.
Stel dat op het bord het getal '4' staat, beide personen zullen die 4 naar hun kladblad overschrijven, hier 1 bij optellen (=5) en het resultaat op het bord gaan schrijven. Als ze beide klaar zijn zal er op het bord '5' staan, terwijl je toch 2 keer hebt verhoogt. Dit probleem wordt vaak met de term 'race condition' aangegeven. Er zijn 2 threads die exact hetzelfde doen met dezelfde input en de ene overschrijft het resultaat van de ander. Een oplossing hiervoor is een lock. Als je de personen voor de optelsom vraagt om op het bord eerst een vinkje te zetten, als er al een vinkje staat moet je wachten tot dit vinkje weg is om zelf een vinkje te kunnen zetten. Van zodra je het vinkje hebt gezet (begin van de lock) kopieer je wat er op het bord staat naar je kladblad, tel je er 1 bij op, schrijf het resultaat op het bord en verwijder je vinkje (end synclock). HIermee zal je als resultaat altijd 6 krijgen.


Voor een formulier kan je het voorbeeld nemen dat er in de klas 1 speciale persoon is die heel goed kan tekenen en rekenen, de rest kan alleen maar rekenen. Als je iets visueel wilt weergeven is dit enkel die ene persoon die dit kan doen. Als die ene persoon druk bezig is met iets te berekenen komt zijn tekenwerk achter en in een programma krijg je dit te zien als een formulier dat niet reageert. Je kan echter wel naar die ene persoon een berichtje (delegate) sturen dat hij iets moet doen. Met behulp van de property ´InvokeRequired` weet je welke persoon er bezig is met de code, als dit 'false' geeft ben je al bezig met de tekenaar, je kan dus perfect iets tekenen (een label aanpassen of iets anders op je formulier doen). Als dit 'true' geeft ga je niet kunnen tekenen. Je weet echter wel wat er nodig is om te tekenen, die code steek je in een envelope (delegate) en stuur je door naar de tekenaar (met de methode Invoke), de tekenaar zal die envelope ontvangen en uitvoeren, zo krijg je dus getekend wat je wilt.


Nu dit allemaal achter de rug is kan ik terugkeren op je probleem:
Als je in je formulier een lijst bijhoudt met de clients die je wilt aanspreken kan je in je backgroundworker telkens die lijst overlopen. In je formulier pas je die lijst in het geheugen aan. Iedere keer je backgroundworker de lust opnieuw start zal die zien dat de lijst minder items bevat en enkel dat uitvoeren. Hier moet je wel rekening houden dat je de lijst kan aanpassen terwijl iemand anders bezig is met hem aan het overlopen. Er zijn 2 mogelijke oplossingen:
- De eerste oplossing die waarschijnlijk in je opkomt is dat je het overlopen van de array en het aanpassen van de array in een synclock steekt. Dit is een normale reactie, je moet echter wel rekening houden dat het nemen van een synclock niet zo snel is en het aanpassen van die array niet zo vaak gebeurd, dit kan dus iets beter;
- Aan de andere hand kan je de array in het geheugen meteen overschrijven met een andere array (niet intern aanpassen maar meteen overschrijven). Als iemand bezig is met het overlopen van de oude array zal die gerust verder kunnen doen, iemand die nog moet beginnen zal dan enkel de nieuwe array zien.

Na al dit gepraat een code voorbeeld:

Visual Basic Code:
Public Class MyForm

Private clientlijst As String()

Public Sub startknop_click(sender As Object, e As EventArgs) Handles startknop.lick
clientlijst = { "client1", "client2", "client3" }
backgroundWorker1.RunWorkerAsync()
End Sub

Public Sub wijzigLijst_click(sender As Object, e As EventArgs) Handles wijzigLijst.Click
Dim tmp = New List(Of String)()
tmp.Add("client1")
tmp.Add("client3")

Dim a = tmp.ToArray()
clientlijst = a ' geen gevaar op race condition, een toekenning is atomair
End Sub

Private Sub backgroundworker1_dowork(sender As Object, e As DoWorkEventArgs) Handles backgroundworker1.DoWork
For Each client In clientlijst
Try
Poll(client)
Catch ex As Exception
If invokerequired Then
invoke(Function() toonfout(ex))
Else
toonfout(ex) 'zou niet mogen voorkomen maar dit is een goede gewoonte om zo te werken
End If
End Try
Next
End Sub

Private Sub toonfout(ex As Exception)
resultaatTextbox.AppendText(ex.Message)
End Sub

End Class



Vraag gerust om meer informatie, werken met meerdere threads is niet eenvoudig en dit is heel veel informatie om mee te beginnen

#3 Coldrestart

Coldrestart

    Starting Developer

  • Leden
  • 4 berichten
    Laatst bezocht 14 feb 2020 22:28

Geplaatst op 04 februari 2020 - 20:34

Dag Supervos,

Alvast bedankt voor deze erg gedetailleerde uitleg!
Vele zaken zijn me eindelijk duidelijk geworden met jouw post.

Ivm de "atomaire" instructies, als ik het goed begrijp, ga je enkel een referentie (32bit - wat in de "stack" zit) van je object doorgeven, ipv een volledig datatype.

Ik heb alvast jouw voorbeeld geprobeerd, en het werkt!
M'n compiler gaf juist een foutje aan, na de invoke, Function zou Sub moeten zijn, omdat "toonfout" geen functie is en niets teruggeeft.

Ik ga nog wat verder oefenen en testjes maken.

Nogmaals bedankt!

Groeten,
Coldrestart.

#4 Supervos

Supervos

    Guru Developer

  • Leden
  • PipPipPipPipPipPip
  • 1409 berichten
    Laatst bezocht vandaag, 13:11
  • LocatieBrugge

Geplaatst op 04 februari 2020 - 21:22

Dag Coldrestart,

Foutjes kunnen gebeuren als je uit de losse pols schrijft ;)


Ik ben blij dat het allemaal duidelijker is geworden, daarvoor zijn we hier ook.

Een referentie (of pointer) is inderdaad 32 bit. Je moet echter opletten met denken in stack en heap want pointers en value types (of structures) kunnen op beide leven.

#5 Coldrestart

Coldrestart

    Starting Developer

  • Leden
  • 4 berichten
    Laatst bezocht 14 feb 2020 22:28

Geplaatst op 05 februari 2020 - 19:47

Dag Supervos,

Geen probleem hoor, ik heb het er maar bij gezet moest iemand anders ook het voorbeeld willen uitvoeren.
Ik ben al super blij dat je al die moeite doet om zo'n voorbeeld aan te maken, het maakt het een pak eenvoudiger om het te begrijpen.

Oei, dat wist ik niet.

Ik heb nog een bijkomende vraag over jouw voorbeeld.
Verbeter mij als ik het niet goed heb;
Als er een fout optreedt, geef je die mee (indien nodig) via een invoke - het aanspreken van een delegate.

Als ik nu een "gewone" delegate wil gebruiken, dan moet ik eerst die delegate publiek "beschrijven" .
Een beetje zoals een klasse, die moet je ook eerst functioneel opbouwen en nadien als object gaan "aanmaken".

Maar in jouw voorbeeld is dit héél eenvoudig met 1 regel code weergegeven.
Hoe komt dat? Die invoke is toch ook gewoon een delegate?
Of maakt visual studio dat op de achtergrond aan?
Of zijn er meedere soorten delegates?


Visual Basic Code:
Public Class Form1

'' voorbeeld van een delegate
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Dim deleg As New DelegBereken(AddressOf bereken)

'Als je een andere functie wil gebruiken, overschrijf deleg door bereken2
'deleg = AddressOf bereken2


MsgBox("Uitkomst is:" & deleg(6, 5))

End Sub

Public Delegate Function DelegBereken(ByVal number1 As Double, ByVal number2 As Double) As Double

Public Function bereken(ByVal getal1 As Double, ByVal getal2 As Double) As Double
Dim uitkomst As Double

uitkomst = getal1 + getal2

Return uitkomst

End Function

Public Function bereken2(ByVal getal1 As Double, ByVal getal2 As Double) As Double
Dim uitkomst As Double

uitkomst = getal1 - getal2

Return uitkomst

End Function
End Class


Alvast bedankt,

Groeten,
Coldrestart.

#6 Supervos

Supervos

    Guru Developer

  • Leden
  • PipPipPipPipPipPip
  • 1409 berichten
    Laatst bezocht vandaag, 13:11
  • LocatieBrugge

Geplaatst op 06 februari 2020 - 20:25

Dag Coldrestart,

Een delegate wordt niet behandeld zoals een klasse, dit is eigenlijk meer een omschrijving van hoe de methode er uit moet zien. De naam die je er aan geeft heeft geen belang en ik dacht zelfs dat ze door elkaar kunnen worden gebruikt.

Volgende zijn dus hetzelfde:
Visual Basic Code:
Public Delegate Function DelegBereken(number1 As Double, number2 As Double) As Double
Public Delegate Function Rekenmachine(nummer1 As Double, nummer2 As Double) As Double


Met de ontwikkeling van .NET 2.0 hebben ze Generics toegevoegd en samen met dit een aantal vooraf gemaakte delegates, zodat je dit niet altijd zelf moet doen:
Visual Basic Code:
Action()
Action(Of T)
Action(Of T1, T2)
'...
Action(Of T1, T2, T3, T4, T5, T6, T7, T8, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16)

Func(Of TResult)
Func(Of T, TResult)
Func(Of T1, T2, TResult)
'...
Func(Of T1, T2, T3, T4, T5, T6, T7, T8, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, TResult)


Daarnaast is er ook toegevoegd dat het expliciet aanmaken van een instantie van een delegate niet meer moet, dit wordt op de achtergrond voor jou gedaan:
Visual Basic Code:
Bereken(New DelegBereken(AddressOf optellen), 1, 2)
Bereken(AddressOf optellen, 1, 2)


Met de komst van .NET 3.0 hebben ze zogenaamde Lambda expressies gemaakt. Deze naam komt uit een bepaalde tak van de wiskunde. Dit hebben ze toegevoegd omdat het soms lastig is om een nieuwe methode te maken om maar voor 1 specifieke reden te gebruiken. Een voorbeeld hiervoor is de List(Of T).Sort. Deze verwacht een delegate, maar als je maar als je dit nodig hebt midden in een zeer lange methode moet je dan zoeken hoe die er uit ziet en kan je de draad van je verhaal kwijt raken. Om dit op te lossen hebben ze dus gezorgd dat je een anonieme methode kan maken
Visual Basic Code:
Public Function Optellen(nummer1 As Double, nummer2 As Double) As Double
  Return nummer1 + nummer2)
End Function

' veel code
Bereken(AddressOf Optellen, 5, 6)
Bereken(Function(nummer1, nummer2) nummer1 + nummer2, 5, 6)


Bij het compileren doet .NET eigenlijk hetzelfde als in het eerste geval, er wordt een nieuwe methode gemaakt met een willekeurige naam, een AddressOf ervan gemaakt en ingepakt in een delegate van het type DelegBereken.
Een ander voordeel dat een Lambda expressie heeft is het opvangen van variabelen (in het Engels een Closure genoemd). Stel bijvoorbeeld volgende code:
Visual Basic Code:
Console.WriteLine("Tekst om te bewaren: ")
Dim mijnTekst = Console.ReadLine()
Bewaar(Sub(dbContext) dbContext.Insert(mijnTekst))


Bij het compileren zal .NET hier niet een nieuwe methode van maken maar een nieuwe klasse die alles in goede banen leidt
Visual Basic Code:
Private Class EenRandomNaam
  Public mijnTekst As String

  Public Sub EenWillekeurigeNaam(dbContext As Context)
	dbContext.Insert(mijnTekst)
  End Sub
End Class


Console.WriteLine("Tekst om te bewaren: ")
Dim willekeurigeVariabeleNaam = New EenRandomNaam()
willekeurigeVariabeleNaam.mijnTekst = Console.ReadLine()
Bewaar(New DelegBewaar(AddressOf willekeurigeVariabeleNaam.EenWillekeurigeNaam))


Samen met deze uitbreiding is de compiler slimmer gemaakt en doet deze aan type inference. Dit betekent dat je het type van de dbContext niet moet opgeven. De compiler kan door de omgeving achterhalen wat het type is en zo je helpen met programmeren.

Let wel, een lambda instructie moet niet altijd naar een delegate gaan, in het geval van een IQueryable zal dit naar een Expression worden vertaald. Een Expression zal de code die je hebt getypt zo goed mogelijk onthouden zodat je deze tijdens het uitvoeren van je programma kan terughalen, voor Entity Framework bijvoorbeeld kan op die manier een query naar de database worden gemaakt. Expressions zijn echter een heel ander hoofdstuk op zich en zal er hier niet op ingaan, weet gewoon dat het bestaat.


Alles wat je hier ziet wordt meestal 'syntactic sugar' genoemd. Zoals je ziet is nog altijd hetzelfde als vroeger maar ze maken de compiler slimmer zodat jij als programmeur minder moet typen. Je mag kiezen om nog altijd op de eerste manier alles te maken, maar op zich is het laatste gemakkelijker, korter en beter. De compiler kan zelfs in bepaalde gevallen kleine optimalisaties uitvoeren waar jij nooit aan had gedacht (voorbeeld is de mijnTekst in de EenRandomNaam als een field opgeven. Dit is iets wat je eigenlijk met properties moet doen maar omdat de compiler de code heeft gemaakt en weet dat niemand hier rechtstreeks aan kan, zal die zich aan deze design guideline zondigen.

* Disclaimer, het is mogelijk dat bepaalde functies beperkter, in een andere vorm of in een andere versie van .NET zijn uitgekomen, geschiedenis is niet mijn sterkste punt, het is echter wel zo dat dit allemaal in de sinds .NET 4.5 zoals hier beschreven moet werken

#7 Coldrestart

Coldrestart

    Starting Developer

  • Leden
  • 4 berichten
    Laatst bezocht 14 feb 2020 22:28

Geplaatst op 06 februari 2020 - 21:06

Dag Supervos,

Ok, interessant, wel handig dat er met de nieuwere compilers veel automatisch/achter de schermen gedaan wordt, echter is het zo wel moeilijker om goed te begrijpen wat er juist op de achtergrond allemaal gedaan wordt.
Zeker als je eens voorbeelden op het internet tegenkomt die op een andere wijze zijn opgebouwd.
Mijn boeken zijn van 2005-2012, daarmee dat er zaken zijn die nu al anders kunnen...

De historiek die je hier opgeeft is écht wel handig voor de beginnende programmeur.
In bijna alle boeken gaat men gewoon direct voorbeelden geven, of het maar op één manier weergeven.
Als je die dan allemaal samen legt weet je niet meer wat nu de juiste manier is, en wat de verschillen zijn of de evolutie ervan is.

Bedankt alvast!!

Groeten,
Coldrestart





Ook met taq VS 2008, VS2015 voorzien

0 gebruiker(s) lezen dit onderwerp

0 lid(leden), 0 bezoeker(s), 0 anonieme gebruikers

Inloggen


[VS 2008] Untitled 1

Met dank aan Jürgen voor de jarenlange inzet van visualbasic.be (anno dec 2000)
Met dank aan Mike en Ronneke voor de jarenlange inzet van vbib.be (anno dec 2010)
Met dank aan PascalBianca voor de jarenlange inzet van vbib.be (anno dec 2016)