Check alle échte Black Friday-deals Ook zo moe van nepaanbiedingen? Wij laten alleen échte deals zien

c# Asynchrone processen moeten een List<> aanvullen, hoe?

Pagina: 1
Acties:

  • Cpt.Sponder
  • Registratie: Januari 2008
  • Laatst online: 06-02-2024
Hoi dames en heren van het (hopelijk) goeie leven!

Al geruime tijd ben ik bezig met mezelf te leren programmeren, en dat gaat me over het algemeen vrij aardig af. Alleen loop ik nu tegen een probleem op waar ik even geen antwoord op weet.

Gebaseerd op het artikel van RDavey heb ik mooi werkende Asynchrone TCP server (en clients).

Nu wil ik hier een logging systeem aan toevoegen. Omdat de server nooit mag "blocken" dacht ik, mooi, kan ik mooi eens uitzoeken hoe ik zelf asynchrone taken kan maken. (Geen kaas van gegeten.)

Ik heb dus een paar classes:
  • Server
  • LogEngine
  • Log (bevat alleen een DateTime en een string)
De server instantieert een LogEngine, welke een asynchrone functie bevat. Deze functie doet niet meer dan een Log toevoegen aan een List<Log> (simpel beginnen he?)

Ik heb de Asynchrone functie gebaseerd op dit artikel

De code thus far:


Daadwerkelijke functie:
C#:
1
2
3
4
5
6
7
8
        private void AddLogEntry(Log l)
        {
            lock (this.logList)
            {
                logList.Add(l); 
            }
            nrLogEntries++;
        }


De Async functie, deze gooit af en toe de "Busy" exception
[
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
        public void AddLogEntryAsync(Log l)
        {
            AddLogEntryDelegate logger = new AddLogEntryDelegate(AddLogEntry);
            AsyncCallback AddEntryCallback = new AsyncCallback(AddLogEntryCallback);

            lock (_sync)
            {
                if (_AddLogEntryIsRunning)
                {
                     throw new InvalidOperationException("Busy...");
                }
                    

                AsyncOperation async = AsyncOperationManager.CreateOperation(null);
                logger.BeginInvoke(l, AddEntryCallback, async);
                _AddLogEntryIsRunning = true;
            }
        }



Async functie, met een poging tot wacht systeempje:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
        public void AddLogEntryAsync(Log l)
        {
            AddLogEntryDelegate logger = new AddLogEntryDelegate(AddLogEntry);
            AsyncCallback AddEntryCallback = new AsyncCallback(AddLogEntryCallback);

            lock (_sync)
            {
                if (_AddLogEntryIsRunning)
                {
                     //throw new InvalidOperationException("Busy...");
                    while(_AddLogEntryIsRunning)
                    {
                        System.Threading.Thread.Sleep(1);
                    }
                }
                    

                AsyncOperation async = AsyncOperationManager.CreateOperation(null);
                logger.BeginInvoke(l, AddEntryCallback, async);
                _AddLogEntryIsRunning = true;
            }
        }


De callback:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
        private void AddLogEntryCallback(IAsyncResult ar)
        { 
            //get the original logger delegate ans the AsyncOperation instance
            AddLogEntryDelegate logger = (AddLogEntryDelegate)((AsyncResult)ar).AsyncDelegate;
            AsyncOperation async = (AsyncOperation)ar.AsyncState;

            //finish asynchronous operation
            logger.EndInvoke(ar);

            //clear running flag:
            lock (_sync)
            {
                _AddLogEntryIsRunning = false;
            }

            AsyncCompletedEventArgs completedArgs = new AsyncCompletedEventArgs(null, false, null);
            async.PostOperationCompleted(delegate(object e) { OnAddEntryCompleted((AsyncCompletedEventArgs)e); }, completedArgs);
        }


Hopelijk kan er iemand antwoord geven op mijn vragen:
  • Klopt mijn conclusie dat als de "busy" exception gegooid wordt het dus zo is dat er een nieuwe call naar de asynchrone functie gemaakt wordt terwijl er nog eentje loopt en niet klaar is?
  • Klopt het dat er nooit 2 tegelijk mogen lopen, immers, er kan maar een functie call tegelijk een List<> bewerken?
  • Is het wel handig om een dergelijk systeem met asynchrone functies te bouwen? zijn er betere alternatieven?
  • Mocht ik hier wel op het juiste spoor zitten, hoe kan ik ervoor zorgen dat de volgende log-actie pas uitgevoerd wordt indien de vorige klaar is?
Ik vraag hier niet om "maak even mijn huiswerk voor me aub" (zit niet eens meer op school haha) dus ik verwacht hier geen code voorbeelden, maar als iemand mij wat duidelijkheid zou kunnen verschaffen wat een betere aanpak is om dit voor elkaar te krijgen, kan die persoon een grote bak met
[no-homo]
LIEFDE
[/no-homo]
verwachten :P

thnx! Sander

edit:
hoofdletters

  • HMS
  • Registratie: Januari 2004
  • Laatst online: 17-11 00:33

HMS

Klopt mijn conclusie dat als de "busy" exception gegooid wordt het dus zo is dat er een nieuwe call naar de asynchrone functie gemaakt wordt terwijl er nog eentje loopt en niet klaar is?
Ja, dat klopt. Hoewel ik niet echt iets heb gedaan met dit async pattern, voelen sommige locks niet echt goed aan. Subjectief ;).
Klopt het dat er nooit 2 tegelijk mogen lopen, immers, er kan maar een functie call tegelijk een List<> bewerken?
List<> is niet thread-safe dus concurrent modification gaat je een probleem opleveren ja.
Is het wel handig om een dergelijk systeem met asynchrone functies te bouwen? zijn er betere alternatieven?
Ik zie niet echt het voordeel van het async maken van iets dat CPU bound is (in dit geval), mocht je het willen loggen naar disk dan kan het wel voordelen hebben. Daarnaast is loggen natuurlijk een probleem dat al 100en keren is opgelost. Pak een library (NLog, Log4net, etc) die async loggen ondersteund en je bent al een heel eind ;).
Mocht ik hier wel op het juiste spoor zitten, hoe kan ik ervoor zorgen dat de volgende log-actie pas uitgevoerd wordt indien de vorige klaar is?
Gebruik een datastructuur dat intern de synchronisatie afhandelt zodat je dat niet buitenom hoeft te doen, op deze manier zie ik zo snel niet een manier om het gegarandeerd goed te laten werken.

Thread-safe datastructures: MSDN: System.Collections.Concurrent Namespace ()

[ Voor 66% gewijzigd door HMS op 23-10-2014 12:37 . Reden: even antwoord op alle vragen ]


  • Cpt.Sponder
  • Registratie: Januari 2008
  • Laatst online: 06-02-2024
SOWHEE! das snel antwoord! thnx man! hier, vangen: bij deze de beloofde bak liefde!

ik merk dat het grootste probleem mijn gebrek aan kennis is! :)
Pak een library (NLog, Log4net, etc) die async loggen ondersteund
Will do, denk dat dit een stuk betrouwbaarder zal zijn dan mijn eigen probeersels!
Hoewel je hier wel natuurlijk een stuk minder van leert...
bijvoorbeeld, kun je dit:
Gebruik een datastructuur dat intern de synchronisatie afhandelt zodat je dat niet buitenom hoeft te doen
eens toelichten? ik weet niet zeker of ik wel begrijp wat je bedoelt. (en dan is het lastig google-en, voor je het weet zit je je verdiepen in niet relevante zaken haha)
Anders gevraagd: wat is het verschil tussen intern en buitenom de synchronisatie regelen?

[ Voor 6% gewijzigd door Cpt.Sponder op 23-10-2014 12:51 ]


  • HMS
  • Registratie: Januari 2004
  • Laatst online: 17-11 00:33

HMS

De datastructeren in de concurrent namespace zijn ontworpen om door meerdere threads gelezen, dan wel bewerkt te worden (tegelijkertijd). Dat heeft voornamelijk performance verbeteringen, met name bij het lezen van de data. In je huidige situatie zou je waarschijnlijk een lock doen waardoor alle schrijf operaties geblokkeerd worden tot dat je alles uitgelezen hebt (als je niet lockt krijg je een ConcurrentModificationException tijdens het itereren), bij de Concurrent datastructuren krijg je een snapshot van de data die je veilig kan uitlezen terwijl de collectie nog steeds bewerkt kan worden.

Deze wiki's zijn misschien handig:
Wikipedia: Thread safety
Wikipedia: Concurrency control

  • Cpt.Sponder
  • Registratie: Januari 2008
  • Laatst online: 06-02-2024
In je huidige situatie zou je waarschijnlijk een lock doen waardoor alle schrijf operaties geblokkeerd worden tot dat je alles uitgelezen hebt (als je niet lockt krijg je een ConcurrentModificationException tijdens het itereren)
yep:
C#:
1
2
3
4
5
6
            lock (_sync)
            {
                if (_AddLogEntryIsRunning)
                {
                     throw new InvalidOperationException("Busy...");
                }


was me al aan het inlezen nav je eerste MSDN link.
Overige links zijn dus minder zoekwerk voor mij, waarvoor dank.
Ik heb voorlopig weer even genoeg huiswerk! Dank dat je mijn neus even de juiste richting in hebt willen duwen!
*duikt snel de grootste bieb ter wereld in! :P

  • Avalaxy
  • Registratie: Juni 2006
  • Laatst online: 20-11 23:40
Waarom moet je systeem async zijn? Het async maken van een CPU-bound functie zorgt er alleen maar voor dat je lagere performance hebt. Het context switchen naar een andere thread levert een hoop overhead op, kan deadlocks opleveren en je kunt onverwachte resultaten krijgen, omdat bijvoorbeeld je threadpool thread niet beschikt over de state van je IIS thread (zoals de current culture).

Gebruik async daar waar het zinnig is (langdurende I/O-operaties), vermijd het daar waar het niet zinnig is.

  • D-Raven
  • Registratie: November 2001
  • Laatst online: 16-10 10:47
Avalaxy schreef op zondag 26 oktober 2014 @ 02:23:
...Het async maken van een CPU-bound functie zorgt er alleen maar voor dat je lagere performance hebt. ...
Dit is echt veel te kort door de bocht, en dat weet je zelf ook wel denk ik. Er zijn zeker wel situaties te bedenken waar het wel zinnig is.
Of het in het geval van de TS zinnig is, ik vermoed van niet. Maargoed hij geeft zelf dan ook aan dat het voor hem voornamelijk een leer experiment is.

@Cpt.Sponder

Ga eens kijken naar het producer/consumer patroon.

  • epic007
  • Registratie: Februari 2004
  • Laatst online: 17-11 15:31
Cpt.Sponder schreef op donderdag 23 oktober 2014 @ 12:24:
...
C#:
1
2
3
4
5
6
7
8
        private void AddLogEntry(Log l)
        {
            lock (this.logList)
            {
                logList.Add(l); 
            }
            nrLogEntries++;
        }

...
• Klopt het dat er nooit 2 tegelijk mogen lopen, immers, er kan maar een functie call tegelijk een List<> bewerken?
Je hebt al een lock op je logList zitten, het lijkt me dus prima als meerdere threads deze functie tegelijk aanroepen. (zet je nrLogEntries dan ook in de lock).

Pas wel op dat je deze lock overal toepast waar je de logList benaderd, bijvoorbeeld wanneer je de logs weg gaat schrijven.

De concurrent collections hier doen dit automatisch voor je.

  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
Buiten het feit wat bovenstaande al gezegd hebben, zou ik voor logging eens kijken naar een kant en klaar log framework zoals log4net of nlog ;)

“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.”


  • Cpt.Sponder
  • Registratie: Januari 2008
  • Laatst online: 06-02-2024
Dank voor jullie antwoorden!
interesting stuff!
Ik heb meerdere sites door zitten lezen waar men log4net en nlog verglijkt.
Op een kopie van mn projectje ga ik eens kijken om nlog te gebruiken, maar zoals jullie al door hadden is het doel van dit projectje "recreatieve educatie" dus ik ga zeker niet opgeven om zelf iets leuks te maken, met de lessen uit deze thread geleerd:
- Thread-safe datastructures
- producer/consumer patroon. (Meerdere oplossingen voor, ik denk dat ik hier een monitor voor ga proberen te maken.)

Nu nog "even" tijd zien te vinden.. :(

Verwijderd

Ik zou eens kijken naar Common.Logging. De ontwikkelaars daarvan hebben juist geprobeerd je te weerhouden van de zoveel verschillende logging frameworks die voor 90% hetzelfde zijn.

Common.Logging heeft zelf een aantal handige adapters, maar ze bieden vooral koppelingen met alle grote bekende frameworks. Zo kun je later switchen of makkelijk verschillende adapters gebruiken in je solution (bijv. front-end en back-end apart).

En over die locking; een producer/consumer lijst die thread-safe is, heeft .NET gewoon ingebouwd met de BlockingCollection<T> (die werkt standaard met ConcurrentQueue<T>). Die lockt ook efficiënter dan een hele lock() op je lijst. In een ander threadje werk je alle items af met GetConsumingEnumerable.

  • R4gnax
  • Registratie: Maart 2009
  • Laatst online: 06-09 17:51
Cpt.Sponder schreef op maandag 27 oktober 2014 @ 10:02:
zoals jullie al door hadden is het doel van dit projectje "recreatieve educatie" dus ik ga zeker niet opgeven om zelf iets leuks te maken, met de lessen uit deze thread geleerd:
Belangrijkste les die je ooit over concurrency zult leren; als er goed geteste libraries of higher-order primitives beschikbaar zijn, ga dan nooit zal iets schrijven op basis van low level primitives.
Pagina: 1