Hoe multithreading loggen met delegates?

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • BasieP
  • Registratie: Oktober 2000
  • Laatst online: 22-07-2024
(alvast sorry voor lange intro, maar ik wil graag duidelijk zijn)

Ik heb een applicatie die kan loggen. Hiervoor gebruik ik een logger class die dmv een stack met unieke 'logsleutels' bepaald welk niveau een logregel heeft.

een logregel kan dus op die manier meerdere logregels bevatten.


Verder heb ik een 'connection' class die mijn databaseverbinding beheerd. Eigenlijk beheerd die class een instantie van mijn businesslaag, en die heeft op zijn beurt weer een databaseverbinding.

Mijn businesslaag wil zijn loginformatie kwijt, en heeft daarvoor een delegate. Bij de initialisatie van mijn businesslaag vul ik de delegate's met logfuncties, die weer gebruik maken van mijn logger class.

Zo is het dus zo dat ik 1 businesslaag heb, die 1 logger instantie kent.

die 1e logger instantie kan ik dmv een functie een niveau laten zakken, door te zeggen dat zijn laatste logregel de parent moet worden van de nu volgende logregels.

Evengoed kan ik hem weer een niveau terug laten gaan zodat alle nu volgende logregels weer hun vorige parent hebben.

Op deze manier kan mijn applicatie zeggen

log IDparent IDbericht
10gebruiker is ingelogd
21scherm 1 wordt geopend
31zoekscherm wordt geopend
41zoekactie wordt gedaan
54SELECT * FROM ....
64SELECT * FROM ....
71gebruiker logt uit


Nu is het dus zo dat ik na het loggen van logregel 1 tegen mijn logger zeg dat hij een niveau dieper moet.
ditzelfde gebeurd bij regel 4, zodat wanneer mijn businesslaag wil loggen, de logregels onder logregel 4 komen.
Daarna zeg ik tegen de logger dat hij weer een niveau omhoog moet.


Nu komt echter het probleem:
Wanneer ik multithreading doe (wat wel eens voorkomt) krijg ik zo'n soort tabel:

log IDparent IDbericht
10gebruiker is ingelogd
21scherm 1 wordt geopend
31zoekscherm wordt geopend
41zoekactie wordt gedaan
54SELECT * FROM ....
64zoekactie wordt gedaan
76SELECT * FROM ....
86SELECT * FROM ....
94SELECT * FROM ....
101gebruiker logt uit


in bovenstaande tabel heb ik een zoekscherm waarop tegelijk 2 zoekacties worden gedaan.

Hoe ga ik daar vanaf komen?
Ik ben er al achter dat ik dan waarschijnlijk mijn logger geen stack moet laten bijhouden, maar moet zorgen dat ik elke logger instantie een parent geef, zodat een logger instantie meerdere childs kan bevatten.

Echter werkt dit opzich prima, behalve met mijn delegates. Het is namelijk zo dat ik maar 1 instantie heb van mijn businesslaag, en de logdelegate daarvan kan ik dus maar 1x zetten.
De logger instantie die ik dus in die delegate gebruik ook.

En daar hield mijn creatieve vermogen ongeveer op....

This message was sent on 100% recyclable electrons.


Acties:
  • 0 Henk 'm!

  • Hydra
  • Registratie: September 2000
  • Laatst online: 21-08 17:09
Als je logged; geef dan in de aanroep mee wat de parent is.

Dus:

Thread1: int rootLevel = Logger.log("gebruiker is ingelogd", 0)
Thread1: Logger.log("scherm 1 wordt geopend", rootLevel)
Thread1: int subLevel = Logger.log("zoekactie wordt gedaan", rootLevel)
Thread1: int subSubLevel = Logger.log("SELECT * FROM ....", subLevel )
etc.

Dat, of zorg dat iedere thread z'n eigen logger-instance heeft. Het is natuurlijk volkomen logisch dat als je 2 threads 1 object laten benaderen die een state bijhoudt deze elkaar in de weg gaan zitten.

https://niels.nu


Acties:
  • 0 Henk 'm!

  • BasieP
  • Registratie: Oktober 2000
  • Laatst online: 22-07-2024
Hydra schreef op woensdag 13 oktober 2010 @ 10:30:
Het is natuurlijk volkomen logisch dat als je 2 threads 1 object laten benaderen die een state bijhoudt deze elkaar in de weg gaan zitten.
Dit is eigenlijk de bewoording die ik zocht toen ik dit topic opende. Probleem is inderdaad dat ik in 1 object (singleton) een state bijhoud, en ik die vanuit twee threads tegelijk wil besturen.


Echter om even in te gaan op de voorgestelde oplossing:
Als je logged; geef dan in de aanroep mee wat de parent is.

Dus:

Thread1: int rootLevel = Logger.log("gebruiker is ingelogd", 0)
Thread1: Logger.log("scherm 1 wordt geopend", rootLevel)
Thread1: int subLevel = Logger.log("zoekactie wordt gedaan", rootLevel)
Thread1: int subSubLevel = Logger.log("SELECT * FROM ....", subLevel )
etc.

Dat, of zorg dat iedere thread z'n eigen logger-instance heeft.
Ik heb de beide oplossingsrichtingen al overwogen, en vind ze beide wel interessant. Sterker nog, de eerste heb ik al werkend gehad in mijn applicatie.

Echter loop ik steeds tegen het gebruik van mijn delegates aan.

Ik heb namelijk 1 object die mijn businesslogic bevat (en via een datalaag naar de database gaat), en deze wil ook af en toe loggen.

Momenteel doe ik dat zo: (C#)
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
internal static Connector getConnector()
{
    if (_connector == null)
    {
        _connector = new Connector(... parameters ....);

        //de delegate 'logdatabase' wordt hier dus gevuld.
        _connector.LogDatabase = new Connector.LogDatabaseDelegate(delegate(string text)
        {
            getLogger().Debug(text);
        });

    return _connector;
}

Doordat ik bij het aanmaken de delegate zet, zal deze ook nooit op de hoogte zijn van de 'state' van de logger.
Wanneer ik 2 threads heb maken ze allebij gebruik van dezelfde connector, en zodoende dus van dezelfde delegate. Ik kan dus ook niet de delegate in thread1 veranderen, want dan is ie ook voor thread2 veranderd.

This message was sent on 100% recyclable electrons.


Acties:
  • 0 Henk 'm!

  • Hydra
  • Registratie: September 2000
  • Laatst online: 21-08 17:09
Dit is dus typisch een voorbeeld waar je geen Singletons kan en wil gebruiken. Je ontwerp is gewoon fout.

https://niels.nu


Acties:
  • 0 Henk 'm!

  • Lethalis
  • Registratie: April 2002
  • Niet online
Hydra schreef op woensdag 13 oktober 2010 @ 10:30:
Dat, of zorg dat iedere thread z'n eigen logger-instance heeft. Het is natuurlijk volkomen logisch dat als je 2 threads 1 object laten benaderen die een state bijhoudt deze elkaar in de weg gaan zitten.
Een eigen instance klinkt logisch en zou mijn eerste keus zijn.

Een andere optie is werken met een buffer, bijvoorbeeld een message queue. Maar dan moet de logger wel in zijn eigen thread draaien om de berichten te verwerken.

Maar al met al vind ik het maar wazig dat de TS in een multi-threaded omgeving graag een single threaded object wil gebruiken.

Een data store als SQL server is immers bij uitstek geschikt om met meerdere instanties te benaderen. Bij .Net heb je dan ook nog connection pooling (zoek het op) waardoor het gebruik van meerdere verbindingen ook nog eens efficient verloopt. Je moet zelf helemaal niet de database verbinding willen beheren. Dat kan het .Net Framework met zijn runtime prima voor jou doen.

[ Voor 23% gewijzigd door Lethalis op 13-10-2010 16:49 ]

Ask yourself if you are happy and then you cease to be.


Acties:
  • 0 Henk 'm!

  • BasieP
  • Registratie: Oktober 2000
  • Laatst online: 22-07-2024
True, het hele singleton verhaal is iets waar ik ik eigenlijk voor heb gekozen omdat mijn eerdere app een 1 thread desktop app was.
Nu ben ik hem aan't ombouwen naar een webapp, waarin ik ook nog eens met ajax meerdere requests tegelijk afvuur.

Al met al heb ik nu dus last van mijn eerdere ontwerpkeuzes.

Op de refactor tour danmaar...

Zoals ik het nu zie heb ik per request een thread. (al dan niet van dezelfde user)
Binnen zo'n thread wil ik alle logacties aan elkaar kunnen relateren.

per thread maak ik dus een nieuwe logger instantie aan die zelf zijn 'diepte' en parent child relaties bijhoud.

Echter kom ik dan weer bij mijn delegates. Mijn businesslogic laag wil ik als singleton houden, omdat mijn app vrij lompe queries afvuurt. De busineaslogic laag bevat een caching mechanisme dat hier heel veel tijd bespaard.

Wanneer ik elke keer een nieuwe instantie maak van die businesslogic laag, (met daarbij een goede logger, en een nieuwe db connectie) dan is die cache weer leeg.

De enige mogelijkheid die ik kan bedenken is dat ik een soort .Clone() functie maakt die een shallow clone maakt van mijn connector en dat ik dan op die clone mijn delegates ga wijzigen.
Maar die brainfart moet ik nog uitwerken....

Tips zijn welkom.

(dit bericht mobiel gemaakt, dus vergeef mij mijn typevautwn)

This message was sent on 100% recyclable electrons.


Acties:
  • 0 Henk 'm!

  • creator1988
  • Registratie: Januari 2007
  • Laatst online: 11-09 14:44
Je code hetzelfde houden hier kan prima; alleen moet je dan de [ThreadStatic] attribute op je instance variable zetten; dan heb je één instance per thread en hoef je daar verder geen logica omheen te schrijven.

Verder heeft elke thread een eigen thread id, die zou je kunnen gebruiken als key om de stacks uit elkaar te houden, dan kan je zelfs toe met een enkele singleton. Gewoon opslaan met het Thread ID erbij.

[ Voor 52% gewijzigd door creator1988 op 14-10-2010 12:44 ]


Acties:
  • 0 Henk 'm!

  • BasieP
  • Registratie: Oktober 2000
  • Laatst online: 22-07-2024
creator1988 schreef op donderdag 14 oktober 2010 @ 12:42:
Je code hetzelfde houden hier kan prima; alleen moet je dan de [ThreadStatic] attribute op je instance variable zetten; dan heb je één instance per thread en hoef je daar verder geen logica omheen te schrijven.

Verder heeft elke thread een eigen thread id, die zou je kunnen gebruiken als key om de stacks uit elkaar te houden, dan kan je zelfs toe met een enkele singleton. Gewoon opslaan met het Thread ID erbij.
dat is zeker het onderzoek waard. Ik ga even kijken hoe dat werkt

This message was sent on 100% recyclable electrons.


Acties:
  • 0 Henk 'm!

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

Niemand_Anders

Dat was ik niet..

Heeft de ThreadStatic nog de oplossing gevormd? Ik vind het welke een mooie quickfix (workaround) voor simpele prototypes. Thread management kan al snel erg complex worden..

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

Pagina: 1