[C#] Thread Synchronisatie

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • bimm
  • Registratie: November 2005
  • Laatst online: 13-01 20:15
Inleiding

Ik heb een probleem en ik kom er niet helemaal uit-- anders hadden jullie mij natuurlijk hier niet gezien. Misschien dat iemand met wat meer threading ervaring mij uit de boot kan helpen.

Wat is het probleem

Een typisch producer/consumer probleem met 1 uitzondering en dat is dat ik niet weet hoeveel consumers er zijn. Nu begrijp ik dat je met een AutoResetEvent netjes alle consumers kan laten wachten met WaitOne tot de producer een Set doet en dus 1 van de consumers verder laat gaan. (Dit klopt ... toch?)
Waar ik niet helemaal uit kom is een goede manier om een OnThreadingDone event uit te sturen als alle workers klaar zijn met werken. Dus niet als 1 thread klaar is maar allen.

Hoe?

Ik heb even wat code in elkaar geknutseld die het probleem (problemen) heel duidelijk demonstreren. Onderstaande code raakt zelfs in een deadlock hoewel het mij ontgaat waarom dit zo is. The OnDone, wat je hier niet ziet, doet gewoon een If this.InvokeRequired and veranderd het forum label om aan te geven dat alle workers klaar zijn.

Mijn vraag (vragen?)
  • Hoe maak je een signaling mechanisme wat netjes werkt als -alle- threads klaar zijn. Ie: ik wil een event op het einde hebben.
  • Wat doe ik nog meer fout hier?
code:
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
74
75
76
77
78
79
delegate void OnDone (object sender, EventArgs e);
    delegate void OnAborted(object sender, EventArgs e);

    class ThreadMaster
    {
        private Thread masterThread;
        private readonly object threadLock;

        public event OnDone Done;
        public event OnAborted Aborted;

        public void threadCore(object data)
        {
            var e = (ManualResetEvent) data;

            try
            {
                var r = new Random();
                Thread.Sleep(r.Next(5) * 1000);
            }
            finally
            {
                e.Set();
            }

        }

        public void startThreadsAsync()
        {
            var threads = new Thread[7];
            var events = new ManualResetEvent[7];

            for (var i = 0; i < 7; i++)
            {
                threads[i] = new Thread(new ParameterizedThreadStart(threadCore)) {Name = "Slave thread"};
                events[i] = new ManualResetEvent(false);
                threads[i].Start(events[i]);
            }

            try
            {
                WaitHandle.WaitAll(events);
                if ((Done != null)) Done.Invoke(this, new EventArgs());
                lock (threadLock) masterThread = null;
            }
            catch (ThreadAbortException)
            {
                foreach (var t in threads) t.Abort();
                WaitHandle.WaitAll(events);
                if ((Aborted != null)) Aborted.Invoke(this, new EventArgs());
                lock (threadLock) masterThread = null;
            }

        }

        public void Abort()
        {
            lock (threadLock)
            {
                if (masterThread != null) masterThread.Abort();
            }
        }

        public void StartThreads()
        {
            lock (threadLock)
            {
                if (masterThread != null) return;
                masterThread = new Thread(new ThreadStart(startThreadsAsync));
                masterThread.Start();
            }
        }

        public ThreadMaster()
        {
            threadLock = new object();
        }

    }

[ Voor 33% gewijzigd door bimm op 19-12-2008 20:29 . Reden: Even een klein iets repareren in mijn post. ]

Ik ook, jij niet?


Acties:
  • 0 Henk 'm!

  • sig69
  • Registratie: Mei 2002
  • Nu online
Dus je wilt eigenlijk gewoon wachten tot alle threads klaar zijn? Misschien moet je eens naar thread.Join() kijken?

Roomba E5 te koop


Acties:
  • 0 Henk 'm!

  • bimm
  • Registratie: November 2005
  • Laatst online: 13-01 20:15
Zover ik de applicatie van .Join zie maakt dit het hele gebeuren juist blocking (tot alles klaar is).

Wat ik juist wil is dat de rest van het programma verder kan en er een notificatie komt wanneer alles klaar is. Niet dat ik dus synchroon ga wachten tot het allemaal klaar is.

Of suggereer je om een producer thread te maken die aan het einde een join op alle threads gaat doen?

Ik snap niet helemaal wat je bedoelt.

Ik ook, jij niet?


Acties:
  • 0 Henk 'm!

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

Niemand_Anders

Dat was ik niet..

Heb je al eens gekeken naar de parallel extensions van Microsoft? Daarin vind je onder andere het object Task waarmee je kunt aangeven dat een process pas verder mag als alle subtaken zijn volbracht (WaitAll).

Een goed startpunt is http://msdn.microsoft.com/en-us/concurrency/default.aspx

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


Acties:
  • 0 Henk 'm!

  • bimm
  • Registratie: November 2005
  • Laatst online: 13-01 20:15
Ik heb er wel aan gedacht maar ik wil zoveel mogelijk materiaal op basis van de traditionele manieren omdat ik het ook wil toepassen onder mono en het compact framework.

Dus de extensions vallen volgens mij af doen.

Ik ook, jij niet?


Acties:
  • 0 Henk 'm!

  • PaulZ
  • Registratie: Augustus 2004
  • Laatst online: 21-05-2024
Ik heb ooit ook eens met dit lopen stoeien (in VB.net). Een 'oplossing' die ik toen tegen ben gekomen is gebruik maken van:
System.Threading.ThreadPool.GetMaxThreads(maxThreads, portThreads) en
System.Threading.ThreadPool.GetAvailableThreads(availThreads, portThreads)
Is niet event-gericht zoals jij dat wilt, maar misschien kan je er een kant mee op...

Vlinders moet je volgen, niet vangen...


Acties:
  • 0 Henk 'm!

  • bimm
  • Registratie: November 2005
  • Laatst online: 13-01 20:15
Een ThreadPool is niets anders dan een handige pool van Threads die klaarstaan om jou te helpen zodat je niet zelf de hele tijd threads hoeft aan te maken (waar een penalty op zit).

Ze helpen echter niet speciaal met synchronisatie.

As always, verbeter mij als ik het fout heb.

Ik ook, jij niet?


Acties:
  • 0 Henk 'm!

  • ValHallASW
  • Registratie: Februari 2003
  • Niet online
Is het niet mogelijk één thread te maken die alle worker threads spawnt en deze met alle worker threads te laten joinen? Dan laat je als al die joins gedaan zijn een event uitsturen.

Acties:
  • 0 Henk 'm!

  • bimm
  • Registratie: November 2005
  • Laatst online: 13-01 20:15
Klopt, dat kan. Theoretisch kan ik bij het aanmaken van een thread ook een nieuw resetevent aanmaken en deze bijhouden in een array.

In die ene thread kan ik gewoon een WaitHandle.WaitAll met die array van ResetEvents meegeven.

Echter, dat betekend dat ik één thread puur heb om te wachten tot alles klaar is?

Ik ook, jij niet?


Acties:
  • 0 Henk 'm!

  • ValHallASW
  • Registratie: Februari 2003
  • Niet online
Een alternatief zou zijn om de eerste (of de laatste) worker thread met alle andere te laten joinen. Of dat lekker werkt weet ik alleen niet. Ik heb verder ook niet veel ervaring met threads, maar een thread die andere spawnt klinkt mij niet onlogisch in de oren.

Ik bedenk me trouwens net dat ik je misschien verkeerd begrijp: wil je dat je threads weten dat er geen werk meer komt (en dat ze dan stoppen) of zet je een vaste batch klaar en wil je weten wanneer deze batch afgerond is?

Acties:
  • 0 Henk 'm!

  • bimm
  • Registratie: November 2005
  • Laatst online: 13-01 20:15
Nee, ik spawn een X aantal threads die allemaal dezelfde zoek opdracht meekrijgen en die er op hun eigen implementatie mee aan de slag gaan. Op een gegeef moment zijn alle verschillende zoek processen klaar en hebben hun resultaten in een collection gezet. Dan wil ik een public event notificatie doen aan de gui thread (en andere luisteraars) die vervolgens de resultaten kan tonen en een nieuwe zoekactie starten.

Ik ook, jij niet?


Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Laatst online: 23:16
[quote]bimm schreef op vrijdag 19 december 2008 @ 13:33:
  • Hoe maak je een signaling mechanisme wat netjes werkt als -alle- threads klaar zijn. Ie: ik wil een event op het einde hebben.
Met een WaitHandle.WaitAll ?

Eventjes schaamteloos geript uit een artikel van Jon Skeet:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using System;
using System.Threading;

class Test
{
    static void Main()
    {
        ManualResetEvent[] events = new ManualResetEvent[10];
        for (int i=0; i < events.Length; i++)
        {
            events[i] = new ManualResetEvent(false);
            Runner r = new Runner(events[i], i);
            new Thread(new ThreadStart(r.Run)).Start();
        }
        
        int index = WaitHandle.WaitAny(events);
        
        Console.WriteLine ("***** The winner is {0} *****", 
                           index);
        
        WaitHandle.WaitAll(events);
        Console.WriteLine ("All finished!");
    }
}

[ Voor 57% gewijzigd door whoami op 19-12-2008 15:19 ]

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

  • bimm
  • Registratie: November 2005
  • Laatst online: 13-01 20:15
Zeker, maar wederom, is het hier niet zo dat vanaf het moment dat de gui thread dus WaitHandle.WaitAll bereikt dat hij blocked, geen ui updates meer doet etc?

Dat wil ik dus niet. Programma moet gewoon verder gaan en ik wil de notificaties dus pas als alles klaar is. Dat kan waarschijnlijk inderdaad dus door 1 thread speciaal te laten blocken tot alles klaar is en dan de event ... maar dit -klinkt- een beetje als overkill om hier een speciale thread voor te hebben. Dat moet anders kunnen...

Ik ook, jij niet?


Acties:
  • 0 Henk 'm!

  • PaulZ
  • Registratie: Augustus 2004
  • Laatst online: 21-05-2024
Voor zoiets had ik dus die pool opgezet. Door geregeld maxThreads en availThreads (timerbased?) te bepalen zie je of alle threads klaar zijn of niet. Of sla ik hier gewoon keihard de plank mis en kan je hier niks mee?

Vlinders moet je volgen, niet vangen...


Acties:
  • 0 Henk 'm!

  • bimm
  • Registratie: November 2005
  • Laatst online: 13-01 20:15
Nee, ja. Waar kijk je dan geregeld in? In welke thread? De GUI? En speciale thread die op de andere threadpool threads wacht? Gebruik je dan nergens anders threadpool threads voor?

De main gui thread moet niet blokkeren maar het programma moet gewoon doorgaan. Ik weet niet hoe jij dat oploste.

[ Voor 1% gewijzigd door bimm op 19-12-2008 15:30 . Reden: Spelling ]

Ik ook, jij niet?


Acties:
  • 0 Henk 'm!

  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
PaulZ schreef op vrijdag 19 december 2008 @ 15:26:
Voor zoiets had ik dus die pool opgezet. Door geregeld maxThreads en availThreads (timerbased?) te bepalen zie je of alle threads klaar zijn of niet. Of sla ik hier gewoon keihard de plank mis en kan je hier niks mee?
Dat is natuurlijk nogal een foutgevoelig mechanisme. Mischien dat je later in je applicatie ook nog een andere thread ergens anders voor gebruikt.
bimm schreef op vrijdag 19 december 2008 @ 15:30:
De main gui thread moet niet blokkeren maar het programma moet gewoon doorgaan. Ik weet niet hoe jij dat oploste.
Je zult inderdaad een andere thread moeten laten wachten, als dit problemen oplevert zou je er inderdaad voor kunnen kiezen om een van de worker threads na het completen van zijn taak, te laten wachten op de andere threads en daarna een event te gooien.

Eigenlijk zou je een soort asyncrone BeginWaitAll willen hebben in dit geval, maar volgens mij bestaat dat niet.

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Laatst online: 23:16
Een taak maken, die op een andere Thread draait, die taak spawnt op zijn beurt opnieuw worker-threads, en de main-thread van die taak laten wachten op de worker-threads van die taak ...

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 16-09 22:43
Die WaitAll kent ook een timeout parameter.

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

  • NetWave
  • Registratie: Oktober 2008
  • Laatst online: 17-03 16:45
Met die WaitAll overload zou het inderdaad moeten lukken.

Als vervolg op de code van Jon Skeet:

C#:
1
2
3
4
5
while (!WaitHandle.WaitAll(events, 100))
{
    Application.DoEvents();
    Thread.Sleep(0);
}


De Application.DoEvents() is noodzakelijk om je GUI thread niet te blokkeren!

[ Voor 15% gewijzigd door NetWave op 19-12-2008 16:55 ]


Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 16-09 22:43
Een timer is voldoende.

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

  • bimm
  • Registratie: November 2005
  • Laatst online: 13-01 20:15
Voor degene die er ook geinteresseerd in zijn, ik heb mijn voorlopige versie even in de startpost neergezet. Deze werkt dus met een master thread die de slaven beheert. support tevens abort & events.

Iemand nog opmerking in de trend van "Wat doe je nu dan toch man?!" (of andere opmerkingen van, zo moet je niet programmeren!)

Ik ook, jij niet?


Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Laatst online: 23:16
  • Waarom is ThreadCore public ?
  • Een thread stop je niet door de Abort aan te roepen; hierdoor stop je je thread nl. op een ongecontroleerde manier. Misschien is hij wel net bezig met het uitvoeren van een bepaalde actie waarvan jij wilt dat die netjes moet afgesloten worden, ipv dat de Thread gekilled wordt doordat er een ThreadAbortException gegooid wordt.
Waarom die lock trouwens ? Volgens mij lock je een object dat niet door meerdere threads kan benaderd worden ?

[ Voor 12% gewijzigd door whoami op 19-12-2008 20:41 ]

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

  • bimm
  • Registratie: November 2005
  • Laatst online: 13-01 20:15
Je hebt gelijk. threadCore en startThreadsAsync mogen allebei private of protected zijn. (vandaar de lower case naam)

Wat betreft de lock, de variable masterThread wordt bewerkt vanuit de threads dus lock ik hem alsvorens een waarde er aan toe te kennen. Niet goed?

Ik geef toe, abort is een beetje ... grof. Dat is beter op zijn plaats voor de Dispose functie misschien als het programma wil afsluiten oid. ik zou threadabort altijd afvangen.

[ Voor 25% gewijzigd door bimm op 19-12-2008 21:03 ]

Ik ook, jij niet?


Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Laatst online: 23:16
Kan je ThreadAbort wel afvangen ?

Zowiezo is het beter om in je Thread te checken of de thread bv moet gecanceled worden, en als dat zo is, kan je je thread 'proper' laten stoppen / afsluiten.

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

  • Sebazzz
  • Registratie: September 2006
  • Laatst online: 16-09 15:42

Sebazzz

3dp

ThreadAbort is niet alleen grof, het is iets wat je zeker niet in productiecode wilt hebben. Als je Thread.Abort called, zal hij in het beste geval in je code een exceptie opgooien, maar in het slechtste geval gooit het een exceptie op in native/unmanaged code. Die kan dingen laten crashen buiten je applicatie om. Hier is genoeg over te vinden op Google.

Quick en dirty methode om een Thread netjes te stoppen:
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
private ReaderWriterLockSlim StopReadWriteLock;
        private bool StopThreads = false;

        private void StopDraadjes() {
            this.StopReadWriteLock.EnterWriteLock();
            this.StopThreads = true;
            this.StopReadWriteLock.ExitWriteLock();
        }

        private void Draadje(object arg) { // thread
            bool StopRequested = false;

            // loopje
            for (;;) {
                // kijken of we moeten stoppen
                this.StopReadWriteLock.EnterReadLock();
                StopRequested = this.StopThreads;
                this.StopReadWriteLock.ExitReadLock();

                if ( StopRequested ) {
                    break;
                }

                // doe iets interessant
                Blaat();
                try {
                      Zoem();
                }
                catch (BadWeatherException ex) {
                       LogException(ex);
                       break;
                }
                // etc
            } // for
        }

[ Voor 11% gewijzigd door Sebazzz op 19-12-2008 21:18 ]

[Te koop: 3D printers] [Website] Agile tools: [Return: retrospectives] [Pokertime: planning poker]


Acties:
  • 0 Henk 'm!

  • bimm
  • Registratie: November 2005
  • Laatst online: 13-01 20:15
Sorry, wat ik bedoelde was het altijd afvangen van de ThreadAbortException. Daarmee kan je nog steeds allerlei resources netjes opruimen, half aangemaakte objecten e.d. Ik denk ook dat in veel gevallen dit voldoende cleaning opties geeft achter de tread aan. Een exception roept immers altijd catch & finalize aan en alles wat je niet opruimt doet garbage collection ook nog.

Alternatief is inderdaad om een Abort te doen via een eigen variable, ja. Volgens mij zijn er echter nog steeds enkele omstandigheden waarop je een threadabortexception kan krijgen. Misschien heb ik het mis. (volgens mij bij het afsluiten van een programma wanneer je background threads hebt draaien)

Ik ook, jij niet?


Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Laatst online: 23:16
Sorry, maar als jij wilt vasthouden aan je Thread.Abort verhaal, doe dat dan, maar kom achteraf niet janken als het fout gaat.
Zowiezo vind ik het gruwelijk fout om een thread op die manier te stoppen, en zomaar altijd op GC vertrouwen .... hmm ... Wat met unmanaged resources ?
When a call is made to the Abort method to destroy a thread, the common language runtime throws a ThreadAbortException. ThreadAbortException is a special exception that can be caught, but it will automatically be raised again at the end of the catch block. When this exception is raised, the runtime executes all the finally blocks before ending the thread. Since the thread can do an unbounded computation in the finally blocks, or call Thread..::.ResetAbort to cancel the abort, there is no guarantee that the thread will ever end.

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Laatst online: 23:16
bimm schreef op vrijdag 19 december 2008 @ 21:17:


Alternatief is inderdaad om een Abort te doen via een eigen variable, ja. Volgens mij zijn er echter nog steeds enkele omstandigheden waarop je een threadabortexception kan krijgen. Misschien heb ik het mis. (volgens mij bij het afsluiten van een programma wanneer je background threads hebt draaien)
When the common language runtime (CLR) stops background threads, after all foreground threads in a managed executable have ended, it does not use Thread..::.Abort. Therefore, you cannot use ThreadAbortException to detect when background threads are being terminated by the CLR.

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

  • bimm
  • Registratie: November 2005
  • Laatst online: 13-01 20:15
Sorry, maar als jij wilt vasthouden aan je Thread.Abort verhaal, doe dat dan, maar kom achteraf niet janken als het fout gaat.
Zowiezo vind ik het gruwelijk fout om een thread op die manier te stoppen, en zomaar altijd op GC vertrouwen .... hmm ... Wat met unmanaged resources ?
Nou nou, wat een taal gebruik. 1) ik zeg niet -vertrouw- op de garbage collection. Ik zeg ruim zelf op in de finalize. Iedereen ruimt toch achter zich op in een Finalize? En voor de rest, ik zei ook dat een eigen variable misschien ook beter is. Threading geeft altijd een overhead. Ik stop meestal mijn threads in de Dispose. Dus ook background threads.

Ik ook, jij niet?


Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Laatst online: 23:16
En hoe doe je dat dan, threads stoppen in je dispose ?
Lijkt me zowiezo ook niet echt een 'best practice'.

Wat bedoel je met 'ruim zelf op in de finalize' ? Bedoel je hiermee de finalizer of de finally clause ?

https://fgheysels.github.io/

Pagina: 1