[C#] Nested backgroundworkers geven out-of-order resultaten

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • Contagion
  • Registratie: Maart 2000
  • Laatst online: 17-08 12:58
Ik ben al een aantal dagen aan het klooien om het volgende scenario voor elkaar te krijgen:

De main thread van een applicatie spawnt een thread (of backgroundworker). Deze thread spawnt op zijn beurt (bijv) 20 backgroundworkers die allemaal parallel een proces moeten uitvoeren. Deze backgroundworkers geven een ProgressReport en de thread erboven (of de 'main backgroundworker') pakt dit event op. Vervolgens weet het dat hij deze gegevens weer verder naar boven moet duwen, dus op zijn beurt vuurt deze thread (of dus de main backgroundworker) zijn eigen ProgressChangedEvent met de gegevens van het binnengekomen child-progressreport.

Het is dus een soort 'bucket brigade' van voortgangsrapporten die van een groep werkers (backgroundworkers) doorgegeven wordt aan de 'opzichter' (main backgroundworker) die het vervolgens naar de 'baas' (main thread) doorgeeft.

Ik gebruik backgroundworkers omdat je dan geen geneuzel hebt met cross-threading exceptions of allerlei vormen van locking en synchronisatie (de progress reports worden namelijk gefired door de 'background thread' zelf MAAR worden opgepakt en de event handler wordt uitgevoerd door de thread die de backgroundthread geinstantieerd heeft). Daarnaast lijkt het mij een goed idee om events te gebruiken met 'voortgangsrapporten' om geen domme idle loops te maken die actief aan het wachten zijn op voortgang (de 'opzichter' en 'baas' hebben zelf ook nog wel wat te doen). Het voordeel is ook dat de werker processen geen kennis hoeven te hebben WIE iets met hun voortgangsrapporten doet, en hoeveel lager (threads) er eventueel boven liggen en of zij extra properties aan hun rapport toevoegen.

MAAR: op de een of andere manier zorgt deze aanpak ervoor dat de voortgangsrapporten niet in de juiste volgorde bij de opzichter (en dan dus ook niet bij de baas) aankomen! Het kan dus zijn dat je bij de progresseventhandler van de main backgroundworker een rijtje percentages van een worker ziet zoals: ...30, 31, 32, 33, 35, 36, 37, 38, 34, 39...

Dat is niet acceptabel voor mijn applicatie (voor welke wel?!?).

Het vreemdste is nog dat als je de 'opzichter' thread er tussenuit haalt en de workers direct aan de mainthread rapporteren, dit fenomeen zich niet voordoet.

Ik heb als alternatief ook geprobeerd dit met een 'AsyncOperation' te doen (samen met de 'AsyncOperation.Post', maar dan gebeurt het zelfde (niet vreemd: een backgroundworker gebruikt intern ook een AsyncOperation)).

Hoe kan het zijn dat de progress event handler de progress reports niet in de juiste volgorde ontvangt en hoe los ik dit elegant op?

Ik heb nu geen compact codevoorbeeld gemaakt om dit te illustreren, eventueel kan ik dat later nog toevoegen aan de post.

Acties:
  • 0 Henk 'm!

  • eek
  • Registratie: Februari 2001
  • Laatst online: 06-04-2020

eek

@MagickNET

Wie berekent je percentage/progress? Ik vermoed dat je 'werker' dit doet? Moet dit niet gebeuren bij je opzichter?

Skill is when luck becomes a habit.


Acties:
  • 0 Henk 'm!

  • Contagion
  • Registratie: Maart 2000
  • Laatst online: 17-08 12:58
Elke werker berekent zijn eigen percentages en vuurt een event. De 'geabonneerde' opzichter reageert daar op en vuurt zijn eigen event met de informatie die hij van de werker gekregen heeft (eventueel met extra properties). De main thread is daar weer abonnee van en doet iets met die informatie.

Hoe zou de opzichter kunnen bepalen hoe ver een werker is behalve door bijvoorbeeld actieft te pollen bij de werker? Dat lijkt met niet elegant, je stuurt de werker juist weg met een soort 'set it and forget it' en wacht tot hij met zijn informatie komt over hoe het gaat.

[ Voor 31% gewijzigd door Contagion op 30-10-2009 13:18 ]


Acties:
  • 0 Henk 'm!

  • [FrEEzEr]
  • Registratie: April 2000
  • Laatst online: 01-08 09:31
De opzichter kan dan toch alle percentages van de werkers optellen en dan berekenen hoeveel het totale percentage is?

Dus:

(aantal werkers * 100) - som(werker percentage) = huidige overall percentage

iMac i7, 8GB | MacMini (2,0-GHz/4GB) | Playstation3 | Wii | Panasonic 42pz85


Acties:
  • 0 Henk 'm!

  • Contagion
  • Registratie: Maart 2000
  • Laatst online: 17-08 12:58
Het gaat niet om de totale percentages, je moet het echt voorstellen als werkers die vertellen wat ze aan het doen zijn en een opzichter die de rapportjes krijgt en direct doorspeelt naar de baas. Het gaat ook niet om dat de opzichter verkeerd rekent of er verder iets mee moet doen, maar dat de rapportjes van de werker hem niet in de juiste volgorde bereiken.

Ik zou denken dat de message queue die tussen threads zit de volgorde van berichten in de queue niet aanpassen (maar hier heb ik dus geen zicht op, want de 'syncOperation.PostOperationCompleted' voert dit gedeelte intern zelf uit).

[ Voor 24% gewijzigd door Contagion op 30-10-2009 13:32 . Reden: Extra informatie ]


Acties:
  • 0 Henk 'm!

  • Niemand_Anders
  • Registratie: Juli 2006
  • Laatst online: 09-07-2024

Niemand_Anders

Dat was ik niet..

Background processen werken op de achtergrond. Heb je jezelf weleens afgevraagd wat een achtergrond process is en welke prioriteit deze van het OS krijgt toegewezen?

Background processen hebben namelijk een lagere prioriteit dan op de voorgrond actieve processen. De background processen zelf hebben wel dezelfde prioriteit, maar dat wil nog niet zeggen dat alle processen dezelfde resources krijgen toegewezen en daardoor kan de ene task eerder klaar zijn dan de tweede.

Als je dan niet wilt moet je of sequentieel programmeren of bijvoorbeeld gebruik maken van de parallels extensions van Microsoft.

Aan de andere kant heb je bijvoorbeeld 25 processen. Dan hoef je toch alleen maar te tellen (bij te houden) hoeveel van die processen al klaar zijn? Als 36 van 50 processen klaar zijn zit je op 72%. De volgorde maakt dan toch helemaal niet uit.

Als die wel uitmaakt zul je anders een soort van 'blocking' loop moeten uitvoeren:
C#:
1
2
3
4
5
for(int i = 0; i < 50; i++)
{
   Task t = completedTasks.WaitFor(x => x.TaskId == i);
    OnTaskComleted(t);
}  

WaitFor is hierbij een IList extension welke net zo lang in een while loop blijft hangen totdat het item met de juiste predicate is gevonden.

If it isn't broken, fix it until it is..


Acties:
  • 0 Henk 'm!

  • roeleboel
  • Registratie: Maart 2006
  • Niet online

roeleboel

en zijn beestenboel

geen oplossing, wel een work-around:

kan de opzichter niet controleren of het gerapporteerde percentage hoger ligt dan bij de laatste rapportage?

Of is het om een of andere reden kritisch dat ieder gemeld percentage doorgegeven wordt?

Acties:
  • 0 Henk 'm!

  • Contagion
  • Registratie: Maart 2000
  • Laatst online: 17-08 12:58
Ik wil juist de 'progresschanged' events afvangen omdat er belangrijke informatie naar de GUI moet worden gestuurd. Ik snap dat de prioriteit van de processen kan maken dat de afhandeling eerder of later zal gebeuren, maar ik begrijp niet waarom als een werker zijn 'progresschanged' event fired en hi wordt niet direct opgepikt door de thread die deze afhandelt, de worker een nieuwe 'progresschanged' event kan firen en dat die dan eerder opgepikt wordt door de handler dan de andere. Ik zou denken dat de events die nog niet afgehandeld zijn in een queue komen en dat die 'in order' door de handler geprocessed wordt, maar dat lijkt dus niet zo.

De 'blocking loop' van de opzichter die wacht tot alles klaar is werkt ook wel naar behoren (een proces kan ook maar 1x klaar zijn, de volgorde daarvan maakt me ook niet uit).

roeleboel: dat kan wel, maar dan zou hij dus moeten blokkeren tot de juiste informatie hem bereikt en dus de events blijven afhangen en cachen tot hij de juiste 'in order' heeft en dan de oude weer afhandelen. Kan, maar is ook niet erg efficient en leidt bijv bij een crashende worker weer tot problemen.

[ Voor 15% gewijzigd door Contagion op 30-10-2009 13:44 . Reden: Extra informatie ]


Acties:
  • 0 Henk 'm!

  • [FrEEzEr]
  • Registratie: April 2000
  • Laatst online: 01-08 09:31
Ff heel snel wat in elkaar gezet, maar heb hier jouw probleem niet?!
Onderstaande is de situatie die je bedoelt toch?
  1. MainThread maakt worker aan
  2. Main worker maakt subworkers aan
  3. Subworker rapporteerd progress
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
using System;
using System.ComponentModel;
using System.Threading;  

namespace TestWorkers
{
    class BackgroundWorkerWithID : BackgroundWorker
    {
        public string ID { get; set; }

        public BackgroundWorkerWithID(string id)
            : base()
        {
            this.ID = id;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            BackgroundWorkerWithID mainWorker = new BackgroundWorkerWithID("Main worker");
            mainWorker.DoWork += new DoWorkEventHandler(mainWorker_DoWork);

            mainWorker.RunWorkerAsync();

            Console.WriteLine("END");
            Console.ReadLine();
        }

        static void mainWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            // create some workers
            for (int i = 0; i < 10; i++)
            {
                BackgroundWorkerWithID worker = Program.createWorker("Worker " + i.ToString());
                worker.RunWorkerAsync();
            }
        }

        static BackgroundWorkerWithID createWorker(string id)
        {
            BackgroundWorkerWithID worker = new BackgroundWorkerWithID(id);
            worker.WorkerReportsProgress = true;
            worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
            worker.DoWork += new DoWorkEventHandler(worker_DoWork);

            return worker;
        }

        static void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            int i = 1;
            while (i <= 100)
            {
                ((BackgroundWorker)sender).ReportProgress(i);

                i++;

                Thread.Sleep(500);
            }
        }

        static void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            // Laat alleen status van worker 2 zien
            if (((BackgroundWorkerWithID)sender).ID == "Worker 2")
            {
                Console.WriteLine("{0}: {1}", ((BackgroundWorkerWithID)sender).ID, e.ProgressPercentage);
            }
        }
    }
}

[ Voor 2% gewijzigd door [FrEEzEr] op 30-10-2009 14:02 . Reden: Extra info ]

iMac i7, 8GB | MacMini (2,0-GHz/4GB) | Playstation3 | Wii | Panasonic 42pz85


Acties:
  • 0 Henk 'm!

  • Contagion
  • Registratie: Maart 2000
  • Laatst online: 17-08 12:58
Goed voorbeeld Freezer. Maar ook jouw voorbeeld draait bij mij de soep in. Vervang je 'thread.sleep' maar eens door:

code:
1
    Fibonacci(30);


waarbij:

code:
1
2
3
4
5
6
private static int Fibonacci(int x)
        {
            if (x <= 1)
                return 1;
            return Fibonacci(x - 1) + Fibonacci(x - 2);
        }


Ik krijg vervolgens de output:
code:
1
2
3
4
5
6
7
8
9
10
11
12
Worker 2: 89
Worker 2: 90
Worker 2: 91
Worker 2: 92
Worker 2: 93
Worker 2: 94
Worker 2: 95
Worker 2: 96
Worker 2: 98
Worker 2: 99
Worker 2: 100
Worker 2: 97 <---


Interessant he? Het gaat ook niet altijd fout, maar draai het proces maar eens een paar keer, of bouw in dat bij een niet opeenvolgend percentage er iets op de console wordt gezet.

Edit:
Wat interessant is is dat de thread ID (Thread.CurrentThread.ManagedThreadId) van de backgroundworker die de andere aanstuurt NIET hetzelfde is als die van de reportprogress handler. Sterker nog, die verandert steeds. Het lijkt er dus op dat hij die progress report threads uit de threadpool pakt en dat ze daarom in andere volgorde kunnen aankomen; bijv de thread die het progress bericht van werker 1 overbrengt naar de opzichter is steeds een andere en daardoor kan het zijn dat het bericht soms later binnen komt...

Bij het voorbeeld waar alleen main de progress reports afhandelt is het wel altijd 'main' die de handling doet..?

Edit2:
Echt een uitstekend voorbeeld Freezer! Bedankt daarvoor, anders had ik mijn crappy code moeten cleanen maar dit voorbeeld geeft het probleem precies weer!

[ Voor 35% gewijzigd door Contagion op 30-10-2009 14:19 ]


Acties:
  • 0 Henk 'm!

  • [FrEEzEr]
  • Registratie: April 2000
  • Laatst online: 01-08 09:31
Graag gedaan :)

Maar wat ik ook probeer ik krijg nooit een foute uitkomst, maar daar heb je natuurlijk weinig aan. Ben nu ook benieuwd wat dit probleem veroorzaakt.

Krijg het nu ook met:

C#:
1
2
3
4
5
6
7
8
9
10
private static int Fibonacci(int x)
        {
            int i = 0;
            while(x-- > 0)
            {
                i++;
            }

            return i;
        }

[ Voor 41% gewijzigd door [FrEEzEr] op 30-10-2009 14:32 ]

iMac i7, 8GB | MacMini (2,0-GHz/4GB) | Playstation3 | Wii | Panasonic 42pz85


Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
Is het niet handiger om bij dit soort berekeningen iets als AsOrdered() van de Parallel Extentions te gebruiken (ok preview in 3.5), in plaats van een progress event te misbruiken?

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • Contagion
  • Registratie: Maart 2000
  • Laatst online: 17-08 12:58
Misschien, maar ik ken AsOrdered() niet en er staat ook geen .NET 3.5 bij, alleen 4 en ik moet wel bij 3.5 blijven. Waarom vind je dat ik de ProgressEvent misbruik? Hoe zou het anders kunnen? Een backgroundworker lijkt me juist bij uitstek de methode om dit te doen, juist vanwege de progress report events. Hoe zou ik anders een Event op een andere thread laten afhandelen dan waar hij gevuurd is.

Ben bezig met het voorbeeld volledig met AsyncOperation te doen, maar ik verwacht daar niet veel van omdat de BGW dit intern ook doet volgens mij. Eventueel zou een asyncOperation.Post dan vervangen kunnen worden door asyncOperation.SynchronizationContext.Send, dit gebeurt niet asynchroon dus kunnen ze niet out-of-order aankomen, maar dan moet de werker altijd wachten tot zijn progress report ook echt afgehandeld is, en dat is niet per definitie noodzakelijk (en vertraagt de werker ook nog eens).

Acties:
  • 0 Henk 'm!

  • Contagion
  • Registratie: Maart 2000
  • Laatst online: 17-08 12:58
Een voorbeeld die op dezelfde manier werkt maar een zelfgemaakte backgroundworker met 'AsyncOperation' gebruikt geeft hetzelfde resultaat als je de progress changed doorgeeft met: asyncOperation.Post

Gebruik je daarentegen AsyncOperation.SynchronizationContext.Send, dan gaat het wel goed (lijkt het nu op het eerste gezicht). Hoewel 'Post' ook logisch klinkt en goed zou moeten gaan, kan SynchronizationContext.Send eigenlijk niet fout gaan, want het werkt niet asynchroon en zal pas doorgaan als het 'progress event' afgehandeld is (in tegenstelling tot post dus en ook hoe de backgroundworker dit doet).

Acties:
  • 0 Henk 'm!

  • [FrEEzEr]
  • Registratie: April 2000
  • Laatst online: 01-08 09:31
Ik zat in de extended backgroundworker al de OnProgressChanged methode te overriden om het synchroon te maken. Blijkt dus ook de oplossing :)

iMac i7, 8GB | MacMini (2,0-GHz/4GB) | Playstation3 | Wii | Panasonic 42pz85


Acties:
  • 0 Henk 'm!

  • Contagion
  • Registratie: Maart 2000
  • Laatst online: 17-08 12:58
Ja dat lijkt inderdaad de oplossing, er worder geen threads gemaakt die tussendoor alleen even de progressreports doorgeven. Ik ben wel benieuwd naar je synchrone override oplossing van de backgroundworker. Dat is een stuk minder complex dan er zelf een schrijven met die AsyncOperation.

Hoewel de backgroundworker NOG een nadeel heeft:
- Als er een exception optreedt in de uitvoer van een backgroundworker, dan is e.Result NIET meer benaderbaar en alleen e.Exception, terwijl je misschien wel wil zien wat er tot nu toe voor resultaat bereikt was. Best suf.

Je kan in je backgroundworker dus niet bijv doen:
code:
1
2
3
4
5
6
7
try
{ doeiets; }
catch 
{
  e.Result = totnutoebereikt;
  throw;
}

Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
Contagion schreef op vrijdag 30 oktober 2009 @ 14:41:
Misschien, maar ik ken AsOrdered() niet en er staat ook geen .NET 3.5 bij, alleen 4 en ik moet wel bij 3.5 blijven. Waarom vind je dat ik de ProgressEvent misbruik?
Nou, die is voor progressie naar GUI-threads en niet voor resultaat naar niet-GUI-threads... :p Implementatie is onbekend (waarschijnlijk die Post-methode die je aanhaalt) en ik zie geen garanties voor optreden in de documentatie.
Hoe zou het anders kunnen?
Hangt sterk van je context af. 20 threads met vrij veel synchronisatie lijken mij in ieder geval in geen enkele situatie een goede oplossing. AsOrdered() misschien dus.
Contagion schreef op vrijdag 30 oktober 2009 @ 15:00:
Een voorbeeld die op dezelfde manier werkt maar een zelfgemaakte backgroundworker met 'AsyncOperation' gebruikt geeft hetzelfde resultaat als je de progress changed doorgeeft met: asyncOperation.Post

Gebruik je daarentegen AsyncOperation.SynchronizationContext.Send, dan gaat het wel goed (lijkt het nu op het eerste gezicht). Hoewel 'Post' ook logisch klinkt en goed zou moeten gaan,
Dit probleem wordt er toch exact behandelt?
Console applications do not synchronize the execution of Post calls. This can cause ProgressChanged events to be raised out of order. If you wish to have serialized execution of Post calls, implement and install a System.Threading..::.SynchronizationContext class.
Ok, Console staat er niet goed denk ik, bedoelt wordt waarschijnlijk threads zonder System.Windows.Forms.WindowsFormsSynchronizationContext.

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • Contagion
  • Registratie: Maart 2000
  • Laatst online: 17-08 12:58
Op zich hoeft er niet extreem veel synchronisatie op te treden, een proces duurt best enige tijd en de backgroundworker kan natuurlijk zelf bepalen hoe vaak hij een progress report stuurt, misschien elke 1%, maar misschien alleen elke 10%.

Er staat op MSDN inderdaad iets over 'out of order' van Console Applications, maar dat is dus niet per defintie het geval. Het gebeurt ook bij een Form. En misschien dat het niet gebeurt bij een form, als het form ook de backgroundworkers opstart, maar kennelijk weer wel als een backgroundworker andere backgroundworkers start.

Op zich ben ik overigens wel erg benieuwd naar andere implementaties van het zelfde principe, dus werkers die rapporteren aan een 'chef' en de chef die de rapporten (evt in gewijzigde of samengestelde vorm) doorgeeft aan de 'baas'.

Mijn idee om het zo te doen komt voort uit het feit dat de main thread de GUI bestuurt en je wil niet dat deze hangt omdat hij moet wachten op een onderliggende taak. De onderliggende taak (chef) bemoeit zich niet met de GUI en zal dus gestart worden om eerst zelf wat werk te verrichten (dat kost enige tijd) en om vervolgens werkers aan de gang te zetten.

Oplossingen zoals 'While (bezig) DoEvents / Thread.Sleep(0);' vind ik heel lelijk. Ik wil dat de 'diepste processen' en dat zijn misschien ook weer slaven van de werkers hun progressie (of soms misschien alleen hun resultaat) eventsgewijs doorsturen. Op die manier hoeven deze diepe processen ook geen rekening te houden met de implementatie van de afhandeling van hun rapportages.Tegelijkertijd moet bij elke laag er de mogelijkheid zijn om meerdere 'gelijken' parallel hun taken af te laten handelen.

Een baas proces kan dus meerdere chefs aansturen, de chefs meerdere werkers en de werkers misschien meerdere slaven en elke laag moet parallel zijn ondergeschikten laten werken maar wel te horen krijgen (in order) als ze klaar zijn, of een bepaalde voortgang bereikt hebben. Een superieur hoeft ook als enige te weten in wat voor vorm (wat en wanneer) zijn ondergeschikten aan hem rapporteren.

Nu is de 'baas' een Windows Forms proces, maar later zal dat een service worden (wat weer problemen met de synchronisatiecontext met zich mee brengt lees ik: http://www.codeproject.com/KB/threads/SynchronizationContext.aspx).

Mocht iemand dus een andere visie hebben hierop dan hoor ik dat graag.

Acties:
  • 0 Henk 'm!

  • R4gnax
  • Registratie: Maart 2009
  • Laatst online: 06-09 17:51
Contagion schreef op vrijdag 30 oktober 2009 @ 17:17:
Mocht iemand dus een andere visie hebben hierop dan hoor ik dat graag.
Voortgangs stamps bijhouden voor de werkers, dus: 1, 2, 3, 4, 5, etc.

Als deze dan out-of-order bij de opzichter aankomen, zeg: 1, 3, ... houdt je deze in een message queue vast todat je het missende item (in dit geval 2) hebt en daarna handel je alles in volgorde af.

Eigenlijk net zoals een simpel netwerk protocol zoals TCP werkt. Had je dus kunnen weten, hè?

Acties:
  • 0 Henk 'm!

  • Contagion
  • Registratie: Maart 2000
  • Laatst online: 17-08 12:58
Bijhouden welke events je nog mist en de rest vasthouden tot je het missende event ziet (out-of-order rapporten zelf re-assemblen om in TCP termen te blijven :)) is eigenlijk nog steeds hetzelfde idee maar dan anders uitgevoerd. Als ik er kort over nadenk kan ik wel een probleem verzinnen met die aanpak en dat is dat misschien de 'WorkComplete' event ook al gefired is voordat je de progress reports op een rij hebt. Aangezien dat een ander event is maakt het de implementatie net weer wat lastiger dan synchroon oplossen. Ik zal er over nadenken, zeker als de synchrone oplossing een grote 'performance-penalty' blijkt te gaan zijn.

Maar ik zat meer te denken aan heel andere oplossingen van het systeem.

Bijvoorbeeld, baas mag op het bord schrijven (main thread/GUI) maar de ondergeschikten niet. Als een worker progress wil melden dan invoked hij de control van het bord en schrijft zijn resultaat er op. Heel lelijk, want ik wil helemaal niet dat iemand lager dan 'baas' uberhaupt weet dat het control 'bord' bestaat, maar je zou het zo kunnen aanpakken (lekker schaalbaar ;)).

Of: 'baas' is een consumer en elke chef, werker, slaaf. etc. zijn producers. Als er iets te consumeren valt zal de baas het uit de queue vissen en bepalen wat hij er mee doet. Nadeel daarvan is weer dat baas dan moet weten hoe de werkers tegen hun chef communiceren, of je moet ook maken dat elke chef bijvoorbeeld consumer is van de worker-producers.

Ik had alleen gedacht dat de .Post operatie intern ook een soort queue zou hebben en dat de events daarom in elk geval in-order aankomen, maar een Post stuurt dus een thread uit de pool op pad om het resultaat bij de superieur te brengen (en de ene thread kan harder lopen dan de andere ;)).

Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
Het lijkt me inderdaad dat er een message queue mist, die normaal de windows-message-queue is en ik denk dat deze intern met PostMessage e.d. werkt. Het probleem is dat de Thread van de eerste BackgroundWorker in het voorbeeld zelfs al is geeindigd voordat de eerste messages binnenkomen. Die Post genereert een extra Thread op de ThreadPool die hem afhandelt, zie dat artikel van CodeProject. Nogal logisch dat de volgorde nooit gaat kloppen op deze manier. Het is wel op te vangen natuurlijk, even quick-and-dirty:

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
    struct UserCallBack {
        public SendOrPostCallback d;
        public Object state;
    }
    class PostSynchronizationContext : SynchronizationContext
    {
        Queue<UserCallBack> q = new Queue<UserCallBack>();
        ManualResetEvent e = new ManualResetEvent(false);

        public override void Post(SendOrPostCallback d, Object state)
        {
            lock(((ICollection)q).SyncRoot) {
                q.Enqueue(new UserCallBack(){d = d,state=state});
                e.Set();
            }
        }

        public void HandleEvents(int count)
        {
            bool nitem = false;
            while (count > 0 && (nitem || e.WaitOne()))
            {
                count -= 1;
                UserCallBack ucb;
                lock(((ICollection)q).SyncRoot) 
                {
                    ucb = q.Dequeue();
                    nitem = q.Count > 0;
                    e.Reset();
                }
                ucb.d(ucb.state);
            }
        }       
    }
...
        static void mainWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            var context = new PostSynchronizationContext();
            SynchronizationContext.SetSynchronizationContext(context);
            // create some workers
            for (int i = 0; i < 10; i++)
            {
                BackgroundWorkerWithID worker = 
                    Program.createWorker("Worker " + i.ToString());
                worker.RunWorkerAsync();
            }
            context.HandleEvents(10 * 101);            
        }

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • Contagion
  • Registratie: Maart 2000
  • Laatst online: 17-08 12:58
Ik vind het anders best een nette 'quick and dirty' oplossing Pedorus :). Het is alleen niet zo heel handig om de 'mainWorker_DoWork' zo te blocken met de 'context.HandleEvents(10 * 101)', zou dat anders kunnen, dus dat de mainworker door kan maar wel events op zijn thread kan ontvangen en uitvoeren? Daarnaast weet hij in dit voorbeeld ook hoeveel events er komen en stopt anders niet (niet handig als de backgroundworker faalt), of je zou iets in moeten bouwen dat hij bij het 'complete' event automatisch de progresshandler sluit (ah vandaar je quick & dirty :)).

De aanzet om de SynchronizationContext te overriden is wel een zeer interessant concept! De volgende oplossing werkt ook. De backgroundworker class zendt een progressreport als een async 'post', in plaats daarvan om de 'order' te garanderen override je de post om hem te vervangen door zijn base.send methode. Dit is volgens mij (mits er niet teveel progress reports doorgestuurd worden) de meest simpele aanpassing waarbij de rest van mijn structuur bewaard kan blijven en de volgorde gegarandeerd wordt door het feit dat de werker stil staat tot hij het rapport ook daadwerkelijk afgeleverd heeft.

code:
1
2
3
4
5
6
7
class PostSynchronizationContext : SynchronizationContext
{
        public override void Post(SendOrPostCallback d, Object state)
        {
                   base.Send(d,state);             
        }
}


Voordat je een backgroundworker instantieert en start doe je daarvoor eerst even:

code:
1
  SynchronizationContext.SetSynchronizationContext(new PostSynchronizationContext());


Ik heb een testje gedaan met 100 backgroundworker die elke 100ms een progress event sturen en dat via een bucket-brigade event (van de backgroundworker naar de main backgroundworker naar de main thread) uiteindelijk in een forms applicatie in 1 van de 100 geassocieerde textboxen schrijven. Het is interessant om te zien dat de processorload met een synchrone doorgifte van progress lager ligt dan wanneer al deze events asycnhroon worden 'gepost'. Kennelijk kost het wachten op de baas (en de chef die er tussen zit) tot hij klaar is met schrijven naar de GUI minder tijd dan het pakken van een thread uit de threadpool om de progress te versturen. Ook als fibonacci wordt gebruikt ipv sleep, zijn de workers net wat sneller klaar. Het blijft wel zaak om dus de event handler niet te gecompliceerd te maken want elke worker wacht natuurlijk tot alle bovenliggende processen klaar zijn

Mijn eerste idee was om de Backgorundworker class zo aan te passen dat ik zelf de Progress event anders afhandel, maar ik weet niet hoe en bovendien kan ik dan denk ik zelf niet bij de gegevens die nodig zijn om de methode op de originele thread the invoken. Dit is eigenlijk veel simpeler, thanks Pedorus!

Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
Contagion schreef op zondag 01 november 2009 @ 01:31:
zou dat anders kunnen, dus dat de mainworker door kan maar wel events op zijn thread kan ontvangen en uitvoeren?
Niet zonder dat je af en toe een soort Applications.DoEvents() aanroept, anders kunnen die events nooit op de juiste Thread worden verwerkt. Een (te) simpele oplossing voor beeindiging is bijvoorbeeld klaar-events tellen met InterLocked, en een Exception gooien om uit HandleEvents te komen (met een oneindige loop ipv een count).
code:
1
2
3
4
5
6
7
class PostSynchronizationContext : SynchronizationContext
{
        public override void Post(SendOrPostCallback d, Object state)
        {
                   base.Send(d,state);             
        }
}

...
Het is interessant om te zien dat de processorload met een synchrone doorgifte van progress lager ligt dan wanneer al deze events asycnhroon worden 'gepost'. Kennelijk kost het wachten op de baas (en de chef die er tussen zit) tot hij klaar is met schrijven naar de GUI minder tijd dan het pakken van een thread uit de threadpool om de progress te versturen.
Dit is iets te mooi om waar te zijn. :p base.Send doet gewoon direct d(state) op de huidige Thread, en er wordt dus helemaal niet gewacht op de baas-thread en ook niks op de baas-thread uitgevoerd (die waarschijnlijk zelfs al is gestopt). In het voorbeeld synchroniseert Console.Write vervolgens de boel, dus zie je het verschil niet als je niet de Threads vergelijkt.
Mijn eerste idee was om de Backgorundworker class zo aan te passen dat ik zelf de Progress event anders afhandel, maar ik weet niet hoe en bovendien kan ik dan denk ik zelf niet bij de gegevens die nodig zijn om de methode op de originele thread the invoken.
Gewoon een kwestie van een eigen message queue gebruiken. Stel dat je de SynchronizationContext in een variabele context hebt opgeslagen, dan kun je bijvoorbeeld ook gewoon direct hetvolgende aanroepen:
C#:
1
2
context.Post(o => worker_ProgressChanged(sender, 
                      new ProgressChangedEventArgs(i, null)), null);

Toen ik deze code teste in het voorbeeld kwam ik na wat scrollen om de progressie tegen te houden erachter dat ik de closure vergeten was, waardoor i meeveranderde. :o Mogelijke oplossing is netjes gebruik maken van de state:
C#:
1
2
context.Post(o=> worker_ProgressChanged(sender, 
                      new ProgressChangedEventArgs((int)o, null)), i);

Zie hier ook 2 voorbeelden van een soort Producer/Consumer-Queues (uitleg is redelijk goed, uitwerking met PulseAll ipv Pulse zie ik niet). Ik heb ook nog een andere implementatie die ook aan Send doet en een soort eigen implementatie van een BlockingCollection/ConcurrentQueue ingebakken heeft:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
    class NonUISynchronizationContext : SynchronizationContext
    {
        protected class UserCallbackNode
        {
            public SendOrPostCallback Callback { get; set; }
            public Object State { get; set; }
            public UserCallbackNode Next { get; set; }
        }

        protected UserCallbackNode QueueFirst { get; set; }
        protected UserCallbackNode QueueLast { get; set; }
        protected ManualResetEvent QueueEvent = new ManualResetEvent(false);

        public override void Send(SendOrPostCallback d, object state)
        {
            Exception ex = null;
            ManualResetEvent m = new ManualResetEvent(false);
            Post(s => { try { d(s); } 
                        catch (Exception e) { ex = e; }  
                        m.Set(); }, state);
            m.WaitOne();
            if (ex != null) 
                throw ex;
        }

        public override void Post(SendOrPostCallback d, Object state)
        {
            var n = new UserCallbackNode() { Callback = d, State = state };
            lock (this)
            {
                if (QueueFirst == null)
                {
                    QueueFirst = n;
                    QueueEvent.Set();
                }
                else
                    QueueLast.Next = n;
                QueueLast = n;
            }
        }

        public void HandleEvents(int count)
        {
            while(count>0 && (QueueFirst != null || QueueEvent.WaitOne()))
            {
                count--;
                UserCallbackNode n;

                lock (this)
                {
                    n = QueueFirst;
                    QueueFirst = n.Next;
                    if (QueueFirst == null)
                    {
                        QueueEvent.Reset();
                        QueueLast = null; //for garbage collector
                    }
                }
                n.Callback(n.State);
            }
        }
    }

Nog weer een andere oplossing met Monitor was net iets minder snel (milliseconden), maar zou dat wel weer kunnen zijn bij meerdere "bazen" (de hier geposte versies werken alleen bij 1 baas). Het mooie van een eigen SynchronisationContext is dat je deze direct ook kan gebruiken met het aankomende TaskScheduler.FromCurrentSynchronizationContext.

[ Voor 3% gewijzigd door pedorus op 01-11-2009 03:37 ]

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • Contagion
  • Registratie: Maart 2000
  • Laatst online: 17-08 12:58
Nog even kort een reactie, de rest lees ik morgen, maar het klopt inderdaad dat de werkers rapporteren als 'werker' ipv de 'main backgroundworker'. Het doorgeven van het event naar de handler van de main backgroundworker echter komt wel bij de main thread. Het venijn zit hem er nu in dat de main backgroundworker zelf dus geen zeggenschap meer heeft over de rapportjes van de werkers. Te mooi om waar te zijn inderdaad ;). Het werkt wel en het originele out-of-order probleem is ook weg, maar goed.

De stukjes context.post die je noemt, gaan die in de doWork routine van de werkers? En krijgt dowork die context mee van zijn 'baas' als argument? Heb je daarvoor al je eigen queue nodig? Of heb je die altijd nodig als je een eigen SynchronisationContext maakt omdat anders niemand de queue pompt?
Ik heb ook een producer/consumer systeem geprobeerd, maar dat leek me te uitgebreid voor wat ik eigenlijk wilde. De rapporten mogen dus ook best synchroon gestuurd worden en je kan aannemen dat de superieur van elke werker-laag blijft bestaan en zelf niet echt werk uit hoeft te voeren (delegeren ;)).

Morgen nog eens kijken.

Edit: Ik heb ook even kort naar de 'Nito Asynchronous LIbrary' gekeken. Voor threads zonder SynchronisationContext kan je het framework gebruiken en hij zorgt ervoor dat de context dan gemaakt wordt en dat er gepompt blijft worden. Hoe ik mijn backgroundworkers in-order (en/of evt synchroon) kan laten reporten met dat framework weet ik niet.

Edit 2: Je voorbeeld blijft nog steeds afhankelijk van 'HandleEvents' die moet weten hoeveel events er komen. Is er niet iets te bedenken dat de 'Reportprogress' event van een backgroundworker er doorheen glipt, maar dat de 'workcomplete' hem stopt? En kan het echt niet alsnog simpeler door slechts de backgroundworker geen .post maar .send te laten doen? Ik blijf zo bij die backgroundworkers hangen omdat ze zo veel compacter zijn dan voor elke methode een async worker schrijven.

[ Voor 27% gewijzigd door Contagion op 01-11-2009 04:47 ]


Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
Contagion schreef op zondag 01 november 2009 @ 03:59:

De stukjes context.post die je noemt, gaan die in de doWork routine van de werkers?
Ja.
En krijgt dowork die context mee van zijn 'baas' als argument?
Of als static variabele.
Heb je daarvoor al je eigen queue nodig?
Als je iets op een andere, bepaalde Thread wilt doen is er al snel een queue nodig.
Of heb je die altijd nodig als je een eigen SynchronisationContext maakt omdat anders niemand de queue pompt?
BackgroundWorkers gebruiken die queue automatisch.
Edit: Ik heb ook even kort naar de 'Nito Asynchronous LIbrary' gekeken. Voor threads zonder SynchronisationContext kan je het framework gebruiken en hij zorgt ervoor dat de context dan gemaakt wordt en dat er gepompt blijft worden. Hoe ik mijn backgroundworkers in-order (en/of evt synchroon) kan laten reporten met dat framework weet ik niet.
Ik heb even gekeken. Die library werkt intern bijna hetzelfde als mijn code, maar is iets netter (of bloated) met meer classes en Sandcastle-commentaar, iets minder snel (microseconden), en handelt Exceptions in Send helaas niet af.
Edit 2: Je voorbeeld blijft nog steeds afhankelijk van 'HandleEvents' die moet weten hoeveel events er komen. Is er niet iets te bedenken dat de 'Reportprogress' event van een backgroundworker er doorheen glipt, maar dat de 'workcomplete' hem stopt? En kan het echt niet alsnog simpeler door slechts de backgroundworker geen .post maar .send te laten doen? Ik blijf zo bij die backgroundworkers hangen omdat ze zo veel compacter zijn dan voor elke methode een async worker schrijven.
Ik had een (te) simpele oplossing daarvoor hierboven met een Exception. Bij Nitro werkt dit trouwens ook zo, met een 'ExitThreadException'. Je haalt count weg uit HandleEvents voor een oneindige loop, en zet daar een try-catch omheen om de ExitThreadException op te vangen. En dan nog even op het juiste moment gooien. Iets als:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
        static int runWorkersCompleted;
        static int totalRunWorkers = 10;
...
            worker.RunWorkerCompleted += new
                RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
...
        static void worker_RunWorkerCompleted(object sender, 
            RunWorkerCompletedEventArgs e)
        { 
            //Interlocked only needed on wrong context, otherwise on same thread
            if (Interlocked.Increment(ref runWorkersCompleted) >= totalRunWorkers)
                throw new ExitThreadException();
        }

In principe zou de mainWorker geen BackgroundWorker moeten zijn, omdat deze nu gaat wachten in de ThreadPool, en dat is niet echt de bedoeling. En als je van die BackGroundWorker af wil, kun je het gewoon uitschrijven, misschien dat dat het principe wat duidelijker maakt:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
        static void mainWorker_DoWork(object sender, DoWorkEventArgs e)      
        {
            var context = new NonUISynchronizationContext();
            var s = Stopwatch.StartNew();
            int wt, it;
            ThreadPool.GetMinThreads(out wt, out it);
            ThreadPool.SetMinThreads(5, 100); //compensate for waiting on ThreadPool
            // create some workers
            for (int i = 0; i < 10; i++)
            {
                if (!ThreadPool.QueueUserWorkItem(no =>
                {
                    for (int p = 1; p <= 100; p++)
                    {
                        context.Post(o => 
                        {
                            if (2 == (int)no)
                                Console.WriteLine("{0}: {1}", (int) no, (int) o);
                        }, p);
                        Fibonacci(30);
                    }
                    if (Interlocked.Increment(ref runWorkersCompleted) >=  runWorkers)
                        context.Post(o => { throw new ExitThreadException(); }, null);
                }, i)) throw new Exception("ThreadPool unavailable");
            }
            context.HandleEvents();
            Console.WriteLine(s.ElapsedMilliseconds);
        }

Enkel deze is vreemd genoeg sneller dan met BackgroundWorker en spawnd standaard minder Threads. :?

[ Voor 3% gewijzigd door pedorus op 01-11-2009 16:09 . Reden: Interlocked kon beter ]

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • Contagion
  • Registratie: Maart 2000
  • Laatst online: 17-08 12:58
Pedorus: nogmaals dank voor je inzichten hierover. Ik geloof dat ik het concept voor elkaar begin te krijgen, alleen wanneer er nu wel of geen eigen 'eventhandler' pomp nodig is moet ik nog gaan ervaren, die onderste laag (de GUI) heeft al een synchronisationcontext en blijft daarom vast werken zonder handleEvents() aan te roepen.

Ik heb nu een voorbeeldje (console) waarbij de 'void main' (main thread dus) een context opzet en de 'hoofd backgroundworker' start, die ook een context opzet en de werkers afvuurt. De main en hoofd backgroundworker blijven vervolgens handleevents() doen en op de 'complete' event wordt de handler vernieitgd (door de exception te gooien). Het werkt inderdaad en elk bericht wordt op de juiste thread en in-order weergegeven. De main en hoofd-worker kunnen door hun handleEvents() alleen niks anders doen, maar je kan die handleEvents vast ook zo schrijven dat hij niet blokkeert en dat je hem als een soort 'doEvents' opneemt in een loop die ook andere zaken afhandelt, maar ik denk niet dat ik zo ver hoef te gaan (vooralsnog :)).

Nog een paar dingen: waarom moet je een exception gooien om hem te vernietigen en kan je niet een bool 'klaar' zetten? Omdat hij anders mogelijk niet uit zijn dequeue komt omdat die in de 'waitone' state blijft staan?

Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
Contagion schreef op zondag 01 november 2009 @ 17:56:
Nog een paar dingen: waarom moet je een exception gooien om hem te vernietigen en kan je niet een bool 'klaar' zetten? Omdat hij anders mogelijk niet uit zijn dequeue komt omdat die in de 'waitone' state blijft staan?
Het is simpelweg de eenvoudigste en snelste oplossing, ondanks het principe dat Exceptions alleen in echt exceptionele situaties horen op te treden. Het kan vast ook anders. :)

In dit geval wil je waarschijnlijk evengoed de hele queue nog afwerken, dus is het makkelijkst om gewoon een event te schedulen, die de boolean dan zou veranderen. Zou ook kunnen, krijg je een conditie als "while (QueueFirst != null || ((!done) && QueueEvent.WaitOne())". Als je direct de boolean wil kunnen zetten krijg je iets als "while (QueueFirst != null || ((!done) && QueueEvent.WaitOne() && (!done))", en zet je done, en doe je dan een QueueEvent.Set(), lijkt al minder handig. Of iets met "while (QueueFirst != null || (QueueEvent.WaitOne() && (!done))", waarbij je done zet in een lock, de Set() doet, en de Reset() onder de conditie (!done) moet. Het gedrag van events die na de exit geenqueued worden zou zo wel iets anders worden trouwens, maar dat is sowieso gek. Al met al lijkt me een 'fake-event' schedulen het handigst. Of je dan een boolean gaat checken, of dat je een exception gooit maakt al minder uit (boolean is 'netter', exception is sneller/makkelijker).

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • Contagion
  • Registratie: Maart 2000
  • Laatst online: 17-08 12:58
Is het nog wel nodig de queue af te werken als je weet dat het laatste event de 'kill' exception gethrowd heeft?

Als het goed is lost jouw implementatie namelijk mijn out-of-order probleem op, dus als de laatste werker eindigt en de queue vernietigt zou er toch ook niks meer in moeten staan? ;)

[ Voor 9% gewijzigd door Contagion op 02-11-2009 00:35 . Reden: 'let me rephrase that...' ]


Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
Theoretisch kan de hele queue dan toch nog vol staan met (bijna) alle Post-acties die er zijn gedaan, omdat deze in een andere Thread wordt afgehandeld? Maar zoveel maakt het sowieso niet uit voor de logica, omdat je niet zomaar iets kan 'vernietigen'. Of de rest van de queue echt afgehandeld moet worden, hangt af van wat erin staat natuurlijk. Als het enkel om progressie-events gaat, dan boeit het niet zo.

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • Contagion
  • Registratie: Maart 2000
  • Laatst online: 17-08 12:58
Ja ok, maar in de queue komen 'progressChanged' maar ook de 'workFinished' events van de backgroundworker. Ik kijk bij de handler van workFinished of alle workers klaar zijn (net zoals jij met die interlocked.incremen == ...) als dat zo is dan gooit hij de queue weg. En dan zou er dus, omdat de volgorde niet verloren gaat, niets meer in moeten staan. De chef thread die de backgroundworkers aanroept is ook de enige die de queue handelt en dus ook degene die de queue kan vernietigen (en het werkt precies zoals ik wil: de post is van de worker, the 'get' wordt uitgevoerd door de chef)

Het werkt in elk geval erg mooi! Dank voor de tijd en moeite die je er in hebt gestopt dit duidelijk te maken en je werkende voorbeelden (freezer idem.). Ik moet nog vragen; ben je nu zo in deze materie thuis dat je zo'n voorbeeld midden in de zaterdagnacht nog even uit de mouw schudt? Ik heb zo lang zitten klootviolen om uberhaupt te achterhalen waarom wat fout gaat en hoe het zit met automatische synchronisatie bij inter-thread communicatie.

* Contagion gaat langs uni om Ir. titel weer in te leveren ;)

[ Voor 15% gewijzigd door Contagion op 02-11-2009 14:26 . Reden: toegevoegd: zelfspot ]


Acties:
  • 0 Henk 'm!

  • CMG
  • Registratie: Februari 2002
  • Laatst online: 10-12-2024

CMG

Ik moet toegeven dat ik een beetje geskipped heb (erg veel data :P), maar was je issue niet dat je meerdere events krijgt van de zelfde thread voordat vorige events zijn afgehandeld? Ik weet niet of je de Invoke(required) functies gebruikt, maar het hoppen naar de UI thread kost best veel tijd... als je dus te snel weer met een update komt en de vorige is nog aan het hoppen loopt je performance daar op stuk. Je zou in je UI een shared Queue(Of Jouw_Event_Data) kunnen aanleggen en daarin je data pompen en dan een worker thread op de UI appje die die queue verwerkt en de UI bijwerkt (hoppen naar UI thread, in 1x alle wachtende data verwerken in UI en dan Loopen op eigen thread).

Groet,

Nick.

NKCSS - Projects - YouTube


Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
Contagion schreef op maandag 02 november 2009 @ 14:23:
Ik moet nog vragen; ben je nu zo in deze materie thuis dat je zo'n voorbeeld midden in de zaterdagnacht nog even uit de mouw schudt? Ik heb zo lang zitten klootviolen om uberhaupt te achterhalen waarom wat fout gaat en hoe het zit met automatische synchronisatie bij inter-thread communicatie.

* Contagion gaat langs uni om Ir. titel weer in te leveren ;)
Haha, nee er is hier flink wat gesurfed om wat meer kennis op te doen, en het helpt wel dat ik al wist hoe windows messages in de GUI werken met getMessage/postMessage/sendMessage en dat er in 4.0 nieuwe dingen zittten aan te komen. Ik verwacht deze kennis binnenkort te gaan gebruiken voor iets anders. En daarnaast is het gewoon vrij lastige materie. Hier gaat iemand van Microsoft bijvoorbeeld behoorlijk in de fout. Even getest op een single-core processor en daar gaat die code een factor 1000 sneller door een Thread.Sleep neer te zetten... :o (En er gaat ook nog iets mis met de Events.)
CMG schreef op dinsdag 03 november 2009 @ 23:36:
Ik moet toegeven dat ik een beetje geskipped heb (erg veel data :P), maar was je issue niet dat je meerdere events krijgt van de zelfde thread voordat vorige events zijn afgehandeld?
De issue was dat het leek alsof meerdere events vanaf dezelfde thread niet in de juiste volgorde werden afgehandeld op een niet-GUI hoofdthread. Achteraf bleek dat ze zelfs niet eens op een en dezelfde Thread werden afgehandeld, maar in de ThreadPool (of bij Send in de eigen Thread).
offtopic:
Volgens snap je nog niet helemaal hoe om te gaan met meerdere Threads gezien deze post (gezien het probleem dat je daar hebt, heb je waarschijnlijk eerder een Invoke vergeten...).

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten

Pagina: 1