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

[C#] Aan events koppelen van locked object?

Pagina: 1
Acties:

Verwijderd

Topicstarter
Ik ben tegen een in mijn ogen heel vreemd probleem aangelopen. Ik heb een class met wat events. Deze class wordt wat rondgeslingerd binnen mijn multi-threaded applicatie en andere classes gebruiken events van deze class. Nu werkt dit prima.

Totdat de maker thread van de class het object gaat locken, en andere threads vervolgens aan events willen koppelen van dit object. Het koppelen van het event duurt dan net zolang totdat de lock op het object met de events vrijgegeven wordt. Nu is de vraag: waarom?

Ik heb een heel simpel voorbeeldje gemaakt die het probleem illustreert:

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
using System;
using System.Threading;

namespace SandBox
{
    class Person
    {
        public event EventHandler TestEvent;

        public Person() { }

        public void DoTestEvent()
        {
            if (TestEvent != null)
                TestEvent(this, EventArgs.Empty);
        }
    }

    class Program
    {
        public void Run()
        {
            Person person = new Person();

            lock (person)
            {
                Thread thread = new Thread(new ParameterizedThreadStart(RunThread));
                thread.Start(person);
                thread.Join();
            }

            Console.ReadLine();
        }

        private void RunThread(object parameter)
        {
            Console.WriteLine("Start thread.");
            
            Person person = (Person)parameter;
            // Hij blijft oneindig hangen op de volgende regel:
            person.TestEvent += delegate(object sender, EventArgs e) { Console.WriteLine("Test event fired."); };

            Console.WriteLine("End thread.");
        }

        static unsafe void Main(string[] args)
        {
            new Program().Run();
        }
    }
}


Het gebruik van een anonymous method is trouwens niet de oorzaak.

/edit
De vraag is niet zozeer of dit een handige manier van aanpak is of hoe je dit kunt voorkomen, maar vooral waarom dit gedrag zich voordoet. Ik zie het event TestEvent namelijk gelijkwaardig aan een public field. Was het een integer die je ophoogt met += dan werkt het uiteraard wel.
Ik heb inmiddels het probleem omzeild wat de boel in mijn ogen ook logischer maakt, maar dan nog ben ik erg benieuwd naar het waarom.

[ Voor 12% gewijzigd door Verwijderd op 19-10-2007 01:10 ]


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

Niemand_Anders

Dat was ik niet..

Zoals je zelf aangeeft lock je het object. Daarmee heb alleen jij (of de betreffende class) exclusieve toegang tot het object. Zolang jij dat exclusieve recht hebt, kun andere objecten niets aan het object veranderen. Een event op een class is eigenlijk een collectie van delegates. Het toevoegen van een event handler aan het object zou dus de betreffende delegate collectie wijzigen.

Wil je er alleen voor zorgen dat bepaalde delen van de code gesynchroniseerd aangeroepen worden, dan kun je het beste Mutex.WaitOne gebruiken (bijvoorbeeld aan het begin van een functie). Ik gebruik alleen lock(this.TestEvent) in de OnXXX methodes om ervoor te zorgen dat als ik een event afvuur niet tussentijds de delegate collectie wijzigd.

Een en ander staat overigens ook beschreven in de MSDN onder thread safe programming. Het belangrijkste bij locking is dat je jezelf altijd zeer goed moet afvragen wat je precies wilt locken. In de meeste gevallen wil je niet het gehele object locken.

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


  • SKiLLa
  • Registratie: Februari 2002
  • Niet online

SKiLLa

Byte or nibble a bit ?

Je lockt het hele object: lock (person), dus dan is het toch vrij logisch dat zolang de lock actief is, er geen andere threads toegang hebben tot dat object; dat is by design ...

'Political Correctness is fascism pretending to be good manners.' - George Carlin


Verwijderd

SKiLLa schreef op vrijdag 19 oktober 2007 @ 08:46:
Je lockt het hele object: lock (person), dus dan is het toch vrij logisch dat zolang de lock actief is, er geen andere threads toegang hebben tot dat object; dat is by design ...
Klinkt niet logisch, ik kan me niet voorstellen dat het op een dergelijke manier geimplementeerd is. Volgens mij is het enkel zo dat als je in de thread wilt locken op Person dat je er dan niet bij kan.

  • mOrPhie
  • Registratie: September 2000
  • Laatst online: 21-11 07:55

mOrPhie

❤️❤️❤️❤️🤍

Verwijderd schreef op vrijdag 19 oktober 2007 @ 08:56:
[...]
Klinkt niet logisch, ik kan me niet voorstellen dat het op een dergelijke manier geimplementeerd is. Volgens mij is het enkel zo dat als je in de thread wilt locken op Person dat je er dan niet bij kan.
Klopt. Maar je cloned het object niet, dus krijgt de nieuwe thread een referentie naar een locked object en zit je dus toch in de locked scope van het object.

toch?

Een experimentele community-site: https://technobabblenerdtalk.nl/. DM voor invite code.


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

Niemand_Anders

Dat was ik niet..

mOrPhie schreef op vrijdag 19 oktober 2007 @ 10:10:
[...]


Klopt. Maar je cloned het object niet, dus krijgt de nieuwe thread een referentie naar een locked object en zit je dus toch in de locked scope van het object.

toch?
Klopt, bij een lock wordt de referentie min of meer op 'read-only' gezet. Maar het komt zelden voor dat je een heel object wilt locken. Meestal worden value-types (string, int, etc), structures (Point) en events gelocked binnen een class. Echter de methode Wait (monitor) heeft wel een timeout mogelijkheid waarmee je een hangende thread kunt voorkomen. Lock (of SyncLock in VB) is slechts een alias naar Monitor Enter en exit.

C#:
1
2
3
4
if (Monitor.Wait(person, 15000) == false)    //maximum wait time 15 seconds
    throw new ThreadStateException("Object is locked");

person.TestEvent += new delegate........

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


  • __fred__
  • Registratie: November 2001
  • Laatst online: 29-11 20:34
Volgens mij ben je conceptueel niet zo handig bezig. Als je al een lock op je hele object wilt zetten, waarom dan niet in de thread die de delegate verbindt. Nu zet je de lock in de calling thread, waarna je dat object doorgeeft aan de callée. Dit is volgens mij vragen om een deadlock situatie.

Verwijderd

Niemand_Anders schreef op vrijdag 19 oktober 2007 @ 11:56:
Klopt, bij een lock wordt de referentie min of meer op 'read-only' gezet. Maar het komt zelden voor dat je een heel object wilt locken.
volgens mij is dit een hele hoop onzin. Je kunt prima vanuit een thread de person instantie benaderen ook al heeft een andere thread een lock op die mutex.

en het komt eigenlijk vrij vaak voor dat je wilt locken op een geheel object. Neem bijvoorbeeld een collectie die door meerdere threads gemuteerd wordt.

Verwijderd

Topicstarter
Bedankt voor de reacties.

Ik lock wel vaker hele objecten. Opzich is daar niets mis mee en afhankelijk van je doel ook juist erg effectief. Maar dat was de vraag niet zozeer, meer waarom je niet aan events kunt koppelen van objecten die door een andere thread gelockt zijn.
Niemand_Anders schreef op vrijdag 19 oktober 2007 @ 11:56:
Meestal worden value-types (string, int, etc), structures (Point) en events gelocked binnen een class.
Je kunt geen value-types locken.

En wat Mark Platvoet zegt klopt inderdaad, je kunt prima een object in een class locken en vanuit een andere class benaderen. Als de ene thread een object lockt dan betekent dat niet dat je er vanuit een andere thread niet bij kunt. Je kunt alles doen wat je wilt (behalve aan events koppelen dus). Thread synchronisatie heeft per definitie alleen zin als alle threads daaraan meedoen. Als ik namelijk bijvoorbeeld public int age; ophoog dan werkt het prima.

C#:
1
2
3
Person person = (Person)parameter;
person.age++;
person.TestEvent += delegate(object sender, EventArgs e) { Console.WriteLine("Test event fired."); };

Hij blijft hier dus hangen op TestEvent +=, en niet op age++.

Maar even terug naar de reden waarom je niet aan events kan koppelen van een gelockt object. Omdat .NET Framework multicast events ondersteunt (vandaar ook +=) ga ik ervan uit dat diep in .NET Framework een lijstje van delegates opgeslagen zit die allemaal afgevuurd moeten worden bij een event. Het is niet meer dan logisch dat deze lijsten thread-safe zijn. Blijkbaar bewaakt .NET Framework deze lijsten met delegates voor events dus door het hele object te locken. Ik vind dat persoonlijk onlogisch. Waarom worden events anders behandeld dan andere members?

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 00:17
Verwijderd schreef op vrijdag 19 oktober 2007 @ 17:33:
Blijkbaar bewaakt .NET Framework deze lijsten met delegates voor events dus door het hele object te locken. Ik vind dat persoonlijk onlogisch. Waarom worden events anders behandeld dan andere members?
Geef je daar al niet zelf je antwoord eigenlijk? Als je delegate een ding is dat door het framework wordt beheerd ( iit een simpele integer ) wil deze er misschien voor zorgen dat de lijst met delegates niet verandert tussen thread switches door?

Just guessing trouwens :)

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.


  • Wmm
  • Registratie: Maart 2002
  • Laatst online: 28-11 17:36

Wmm

farlane schreef op vrijdag 19 oktober 2007 @ 23:26:
[...]


Geef je daar al niet zelf je antwoord eigenlijk? Als je delegate een ding is dat door het framework wordt beheerd ( iit een simpele integer ) wil deze er misschien voor zorgen dat de lijst met delegates niet verandert tussen thread switches door?

Just guessing trouwens :)
Daar dacht ik dus ook aan toen ik de dat deel van de post las.

Je geeft zelf het antwoord idd. Als er bij het toevoegen van een event niet gelocked wordt dan kunnen twee threads die tegelijk events toevoegen elkaar het leven zuur maken. .NET locked dus blijkbaar ook op het object, wat jij dus ook al gedaan hebt. Dus krijg je een deadlock.

Verwijderd

Topicstarter
Wmm schreef op zaterdag 20 oktober 2007 @ 00:38:
Als er bij het toevoegen van een event niet gelocked wordt dan kunnen twee threads die tegelijk events toevoegen elkaar het leven zuur maken. .NET locked dus blijkbaar ook op het object, wat jij dus ook al gedaan hebt. Dus krijg je een deadlock.
Ja precies. Of dat dus het antwoord is kan ik zo niet zeggen, maar dat is het enige wat ik kan bedenken met wat logisch nadenken. Dan blijf ik het vreemd vinden dat .NET het hele object lockt als hij intern met lijsten delegates aan de gang gaat, maar daar hebben ze vast wel een reden voor.

In ieder geval bedankt voor de reacties!

  • EfBe
  • Registratie: Januari 2000
  • Niet online
Object A heeft event E, object B heeft handler H voor event E. Jij lockt A en wil dan B.H binden aan A.E. Dit betekent dat A een reference maakt naar B.H en die opslaat in zichzelf. echter, A is locked, dus je kunt A niet wijzigen wanneer je niet de lock zelf hebt gezet. Ergo, je wacht todat de lock weg is.

Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com


  • farlane
  • Registratie: Maart 2000
  • Laatst online: 00:17
EfBe schreef op zaterdag 20 oktober 2007 @ 10:45:
Object A heeft event E, object B heeft handler H voor event E. Jij lockt A en wil dan B.H binden aan A.E. Dit betekent dat A een reference maakt naar B.H en die opslaat in zichzelf. echter, A is locked, dus je kunt A niet wijzigen wanneer je niet de lock zelf hebt gezet. Ergo, je wacht todat de lock weg is.
En waarom zou hetzelfde maar dan met een integer++ dan wel werken? Bovendien wordt op het moment dat er een handler wordt toegevoegd niet naar een lock gekeken.

[ Voor 8% gewijzigd door farlane op 20-10-2007 17:21 ]

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.

Pagina: 1