[.NET/Win7] Reentrancy in een single-threaded applicatie

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • LeX-333
  • Registratie: Maart 2004
  • Laatst online: 21-11-2016
Ik heb een .NET applicatie onder Windows 7 met user-interface waarvoor het zeer belangrijk is dat de functionele integriteit van de applicatie zo goed als mogelijk gegarandeerd kan worden. Denk hierbij onder andere aan data integriteit en de volgordelijkheid van acties van de gebruiker. Performance, waaronder zelfs de responsiveness van de UI, is veel minder belangrijk. Omdat de applicatie zo belangrijk is is er een uitgebreide automatische testomgeving.

De applicatie is relatief complex, bevat meerdere lagen en is opgezet volgens het “Model View ViewModel” pattern (Wikipedia: Model View ViewModel). Hierdoor is de business-logic netjes gescheiden van de UI. De applicatie doet geen langdurige acties vanaf de UI (de duurste actie kost 10-15 ms) dus hierdoor is het mogelijk om de hele applicatie single-threaded te maken. Door de applicatie op deze manier op te zetten, kunnen race-conditions en andere threading gerelateerde bugs zo goed als uitgesloten worden.

Echter, in een vrij laat stadium komen er allerlei problemen aan het licht die data corruptie, crashes, en deadlocks veroorzaken. Deze problemen zijn in een automatische test eigenlijk niet zichtbaar, maar bij handmatige tests komen ze heel af en toe voor. Ondanks dat ze niet heel vaak voorkomen, zijn de gevolgen vaak wel ernstig en bovendien erg onvoorspelbaar. Hierdoor zijn de problemen die zich voordoen zogenaamde show-stoppers voor de applicatie.

Na enig onderzoek blijkt onderstaande de root-cause van alle problemen die zich voordoen:

De UI-thread van een .NET UI applicatie is een zogenaamde COM STA thread. Dit betekent dat wanneer deze thread op een synchronisatie object moet wachten (bijvoorbeeld een Monitor, AutoResetEvent, of een Mutex), messages in de message queue afgehandeld kunnen worden. Zie ook: http://blogs.msdn.com/cbrumme/archive/2004/02/02/66219.aspx

De applicatie gebruikt zelf geen threads, en dus ook geen synchronisatie objecten. Echter verschillende (.NET) componenten die de applicatie gebruikt hebben intern wel worker-threads en dus ook synchronisatie objecten. Hierdoor kan het impliciet toch gebeuren dat er vanaf de UI-thread kortstondig op een synchronisatie object moet worden gewacht.

De consequentie is bijvoorbeeld dat een functie die via een BeginInvoke op de UI-thread uitgevoerd wordt, halverwege onderbroken wordt door een OnPaint omdat er toevallig (deep-down ergens) op een synchronisatie object moet worden gewacht. Hierdoor kan het gebeuren dat de applicatie zich niet in een consistente state bevind op het moment dat de OnPaint wordt uitgevoerd. Of andersom, de state van de applicatie kan veranderen op ieder moment dat er op een synchronisatie object moet worden gewacht doordat de OnPaint de state van de applicatie aanpast.

Dit kan eenvoudig gereproduceerd worden met onderstaand voorbeeld:

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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
using System.Windows.Threading;

namespace Test
{
    public class Test : Form
    {
        [STAThread]
        static void Main()
        {
            Application.Run(new Test());
        }

        public Test()
        {
            messageList.Location = new Point(13, 13);
            messageList.Size = new Size(267, 225);
            Controls.Add(this.messageList);

            addButton.Location = new Point(205, 241);
            addButton.Size = new Size(75, 23);
            addButton.Text = "Add";
            addButton.Click += OnButtonClick;
            Controls.Add(this.addButton);

            ClientSize = new Size(292, 273);
            Name = Text = "Test";
            FormClosed += OnFormClosed;

            running = true;
            workerThread = new Thread(WorkerThread);
            workerThread.Start();
        }

        private void OnFormClosed(object sender, FormClosedEventArgs e)
        {
            running = false;
            workerThread.Join();
        }

        private void OnButtonClick(object sender, EventArgs e)
        {
            Dispatcher.CurrentDispatcher.BeginInvoke(new Action(OnBeginInvoke));
            Invalidate();

            lock (this)
            {
                enterCount++;
                int n = eventCount++;

                messageList.Items.Add(enterCount + " OnButtonClick Enter " + n);
                lock (synchronizationObject) { /* ... */ }
                messageList.Items.Add(enterCount + " OnButtonClick Leave " + n);

                enterCount--;
            }
        }

        private void OnBeginInvoke()
        {
            lock (this)
            {
                enterCount++;
                int n = eventCount++;

                messageList.Items.Add(enterCount + " OnBeginInvoke Enter " + n);
                lock (synchronizationObject) { /* ... */ }
                messageList.Items.Add(enterCount + " OnBeginInvoke Leave " + n);

                Invalidate();

                enterCount--;
            }
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            lock (this)
            {
                enterCount++;
                int n = eventCount++;

                messageList.Items.Add(enterCount + " OnPaint Enter " + n);
                lock (synchronizationObject) { base.OnPaint(e); }
                messageList.Items.Add(enterCount + " OnPaint Leave " + n);

                enterCount--;
            }
        }

        private void WorkerThread()
        {
            while (running)
            {
                lock (synchronizationObject) { Thread.Sleep(250); }
            }
        }

        private int eventCount, enterCount;
        private object synchronizationObject = new object();
        private ListBox messageList = new ListBox();
        private Button addButton = new Button();

        private volatile bool running;
        private Thread workerThread;
    }
}


Start de applicatie en druk snel achter elkaar een aantal keer op de “Add” knop. Na een paar seconden zal de lijst gevuld zijn met items. Als alles goed is gegaan zouden er alleen items mogen staan die beginnen met een “1”. Echter wanneer deze test op Windows Vista of nieuwer wordt uitgevoerd zullen er regelmatig items tussen zitten die met een “2” beginnen, bijvoorbeeld:

1 OnButtonClick Enter 8
2 OnPaint Enter 9
2 OnPaint Leave 9
1 OnButtonClick Leave 8

Hieruit kan afgeleid worden dat terwijl OnButtonClick wacht op een synchronisatie object OnPaint al uitgevoerd wordt.

Dit probleem wordt erger op het moment dat de applicatie andere stimuli van buitenaf krijgt. Probeer bijvoorbeeld eens heel vaak op de “Add” knop te drukken en dan terwijl de lijst vult met de muis boven de knop in de taakbalk hangen en dan rechts klikken op de popup van de taakbalk. Nu zullen er in de lijst soms zelfs items zitten die met een “3” beginnen, bijvoorbeeld:

1 OnButtonClick Enter 41
2 OnPaint Enter 42
3 OnBeginInvoke Enter 43
3 OnBeginInvoke Leave 43
3 OnPaint Enter 44
3 OnPaint Leave 44
2 OnPaint Leave 42
1 OnButtonClick Leave 41

Hieruit kan afgeleid worden dat OnButtonClick is onderbroken door een OnPaint, maar vervolgens de OnPaint is onderbroken door een OnBeginInvoke gevolgd door een OnPaint. Het is ook goed om te beseffen dat de “lock (this)” statements hier niet tegen beschermen omdat alles vanaf de dezelfde thread wordt aangeroepen.

Let op: dit is geen bug, dit is by-design (zie het bovengenoemde MSDN blog). Het is dus de bedoeling dat hier in het ontwerp van een applicatie rekening mee gehouden wordt. Dat is in dit geval dus niet gebeurd.

Dit probleem kan gereduceerd worden door zo veel mogelijk events op een worker-thread af te handelen. Echter alle acties op de UI, clipboard, shell, enz. zullen dan weer op de UI-thread gezet moeten worden waardoor de applicatie alsnog een complexe multi-threaded applicatie wordt met alle aanverwante threading problemen. Bovendien lost dit het probleem niet op en moet er heel goed opgelet worden hoe de synchronisatie tussen de worker-thread en de UI-thread gedaan wordt, aangezien dit alsnog voor problemen kan zorgen.

Daarom de volgende vragen:
Is bovengenoemde probleem bekend? Zo ja, hoe wordt er mee omgegaan en wat is de beste manier om het in het design mee te nemen?
Bovenstaand probleem beschouwende, is het eigenlijk wel verstandig om een mission-critical applicatie op basis van .NET te maken? Zo niet, wat is dan het (beste) alternatief?

Too many people, making too many problems


Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 11-09 12:01
Je zegt dat je applicatie single threaded is, en daarbij vemeld je ook dat er verschillende componenten background threads gebruiken ... maw je applicatie is niet single threaded en je zou dus aan synchronisatie moeten doen, of niet die componenten gebruiken. ( Daarnaast, als je echt single threaded werkt gebruik je geen BeginInvoke omdat je altijd op de UI thread zit : je mag de functies in je UI direct aanroepen. )

Anyways, je kunt geen multithreading doen zonder de nadelen die eraan zitten.

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!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
offtopic:
Ik vind dit voorbeeld wat lelijk omdat WPF (Dispatcher) en Forms door elkaar gebruikt worden. Maar dat maakt voor het concept niet uit.

Een STAThread hoort zich nooit in een lock te bevinden, dus .NET zorgt ervoor dat er een message loop gestart wordt in deze situatie, omdat je anders deadlocks krijgt. Zie bijv. ook hier en hier. Misschien is het mogelijk om zonder STAThread te werken (dus met niks of MTAThread, hoewel dat in principe niet mag in combinatie met Windows Forms). Zo niet, dan zul je hier rekening mee moeten houden, andere smaken zijn er niet echt denk ik. :p

Ik moet zeggen dat ik dit ook even op moest zoeken. Het lijkt erop dat alleen bepaalde events zoals WM_PAINT, die normaal geen state-verandering veroorzaken worden afgehandeld. Of misschien zelfs alleen WM_PAINT. Het is vreemd dat in jouw geval deze wel een state-verandering veroorzaken. Ik krijg bijvoorbeeld met OnClick het niet aan de gang, dus vandaar dat dit meestal geen problemen veroorzaakt.

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • Haan
  • Registratie: Februari 2004
  • Laatst online: 11-09 16:33

Haan

dotnetter

Allereerst hulde voor de uitgebreide startpost!
Nu heb ik zelf vrijwel geen ervaring met .NET desktop applicaties, maar wel met web applicaties die per definitie multithreaded zijn.

Ik heb twee opmerkingen:
1. Het gebruik van threads is met Task Parallel Library(TPL) een stuk eenvoudiger geworden. Vereist wel .NET 4.0, maar dat zou geen probleem moeten zijn, aangezien dat al 2 jaar gereleased is. In jouw voorbeeld is het starten van de worker thread dan gewoon
C#:
1
2
3
// using System.Threading.Tasks

Task.Factory.StartNew(WorkerThread);

that's it.

2. waarom lock je de ene keer this en de andere keer een sync object? Als ik overal het sync object lock, treedt je probleem niet meer op..

Kater? Eerst water, de rest komt later


Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
Haan schreef op dinsdag 01 mei 2012 @ 07:46:
2. waarom lock je de ene keer this en de andere keer een sync object? Als ik overal het sync object lock, treedt je probleem niet meer op..
Gebruik je soms XP om te testen? Locks zijn reentrant binnen dezelfde thread, en locks doen Monitor.Enter wat in een STA-thread een message pump doet lopen die iig WM_PAINT afhandelt onder Windows 7.

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • Haan
  • Registratie: Februari 2004
  • Laatst online: 11-09 16:33

Haan

dotnetter

Nee XP gebruik ik al jaren niet meer ;) Getest onder Windows 7 (was wel een debug sessie vanuit Visual Studio, mocht dat uitmaken)

Kater? Eerst water, de rest komt later


Acties:
  • 0 Henk 'm!

  • H!GHGuY
  • Registratie: December 2002
  • Niet online

H!GHGuY

Try and take over the world...

Haan schreef op dinsdag 01 mei 2012 @ 07:46:
2. waarom lock je de ene keer this en de andere keer een sync object? Als ik overal het sync object lock, treedt je probleem niet meer op..
Om die monitor.enter die het probleem veroorzaakt te hebben?
Jij hebt gewoon de root cause van het probleem weggenomen, maar daarmee geen oplossing voor de TS zijn probleem gegeven.

ASSUME makes an ASS out of U and ME


Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
Haan schreef op dinsdag 01 mei 2012 @ 14:39:
Nee XP gebruik ik al jaren niet meer ;) Getest onder Windows 7 (was wel een debug sessie vanuit Visual Studio, mocht dat uitmaken)
Hmm, ja, ik heb dat ook onder Windows 7, de dubbele lock die is ontstaan wordt waarschijnlijk volledig weggeoptimaliseerd. Als je regel 49 wegcomment krijg je het probleem weer gewoon... :p

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • LeX-333
  • Registratie: Maart 2004
  • Laatst online: 21-11-2016
farlane schreef op maandag 30 april 2012 @ 23:02:
Je zegt dat je applicatie single threaded is, en daarbij vemeld je ook dat er verschillende componenten background threads gebruiken ... maw je applicatie is niet single threaded en je zou dus aan synchronisatie moeten doen, of niet die componenten gebruiken. ( Daarnaast, als je echt single threaded werkt gebruik je geen BeginInvoke omdat je altijd op de UI thread zit : je mag de functies in je UI direct aanroepen. )

Anyways, je kunt geen multithreading doen zonder de nadelen die eraan zitten.
Uiteraard is het een multit-hreaded applicatie (volgens mij is het ook niet mogelijk om met .NET een echte single threaded applicatie te maken, want de meest simpele .NET applicatie heeft volgens de task manager al 4 threads). Het punt is dat er componenten gebruikt worden die intern een worker thread hebben. Deze componenten hebben dit uiteraard wel netjes opgelost met locks, maar doen daardoor wel een lock op de UI thread, die daardoor impliciet reentrant wordt.
pedorus schreef op dinsdag 01 mei 2012 @ 01:21:
Een STAThread hoort zich nooit in een lock te bevinden, dus .NET zorgt ervoor dat er een message loop gestart wordt in deze situatie, omdat je anders deadlocks krijgt. Zie bijv. ook hier en hier. Misschien is het mogelijk om zonder STAThread te werken (dus met niks of MTAThread, hoewel dat in principe niet mag in combinatie met Windows Forms). Zo niet, dan zul je hier rekening mee moeten houden, andere smaken zijn er niet echt denk ik. :p
Volgens mij zit je vast aan een STA thread zodra je WinForms of WPF gebruikt, misschien dat het in sommige gevallen ook met een MTA werkt, maar dat lijkt me ook vragen om problemen.
pedorus schreef op dinsdag 01 mei 2012 @ 01:21:
Ik moet zeggen dat ik dit ook even op moest zoeken. Het lijkt erop dat alleen bepaalde events zoals WM_PAINT, die normaal geen state-verandering veroorzaken worden afgehandeld. Of misschien zelfs alleen WM_PAINT. Het is vreemd dat in jouw geval deze wel een state-verandering veroorzaken. Ik krijg bijvoorbeeld met OnClick het niet aan de gang, dus vandaar dat dit meestal geen problemen veroorzaakt.
Wat er bijvoorbeeld gebeurd is dat de OnPaint begint te tekenen terwijl de applicatie zich nog niet in een consistente state bevind, bijvoorbeeld:

C#:
1
2
3
4
5
6
7
8
9
10
void ReadNextImage()
{
    MemoryPool.FreeBuffer(pixelDataPtr);
    
    width = reader.NextWidth();
    height = reader.NextHeight();

    pixelDataPtr = MemoryPool.AllocBuffer(width * height * 4);
    reader.ReadNextImage(pixelDataPtr);
}


Als MemoryPool.AllocBuffer() een lock pakt, kan het zijn dat er op dat moment een OnPaint wordt uitgevoerd terwijl de pixelDataPtr naar een ongeldig of te klein stuk geheugen wijst. Natuurlijk kun je dit dan weer omschrijven zodat het enigzins goed gaat, maar dit zit natuurlijk overal en vaak in componenten die gebruikt worden door andere componenten die weer mogelijk gebruikt worden vanaf de UI thread, iets wat ook een beetje door het MVVM pattern wordt uitgedragen.

De vraag is hoe kun je dit dan het beste in je design meenemen zonder dat het meteen een complexe multi-threaded applicatie wordt wat waarschijnlijk net zoveel bugs introduceert als dat ik probeer op te lossen?
Haan schreef op dinsdag 01 mei 2012 @ 07:46:
1. Het gebruik van threads is met Task Parallel Library(TPL) een stuk eenvoudiger geworden. Vereist wel .NET 4.0, maar dat zou geen probleem moeten zijn, aangezien dat al 2 jaar gereleased is.
Klopt, maar ook dit geeft reentrancy problemen.
Haan schreef op dinsdag 01 mei 2012 @ 07:46:
2. waarom lock je de ene keer this en de andere keer een sync object? Als ik overal het sync object lock, treedt je probleem niet meer op..
Dat was een voorbeeldje om te laten zien dat de lock niet helpt om het probleem op te lossen, je zou verwachten dat je door een lock te gebruiken maar 1 kritische sectie zou mogen betreden, maar dat gaat in dit geval niet op. Als je overal het sync object gebruikt treed het nog steeds op, maar je kunt het niet meer zien omdat de code die het moet aantonen niet meer werkt.

Too many people, making too many problems


Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
LeX-333 schreef op dinsdag 01 mei 2012 @ 22:41:
Wat er bijvoorbeeld gebeurd is dat de OnPaint begint te tekenen terwijl de applicatie zich nog niet in een consistente state bevind, bijvoorbeeld:

C#:
1
2
3
4
5
6
7
8
9
10
void ReadNextImage()
{
    MemoryPool.FreeBuffer(pixelDataPtr);
    
    width = reader.NextWidth();
    height = reader.NextHeight();

    pixelDataPtr = MemoryPool.AllocBuffer(width * height * 4);
    reader.ReadNextImage(pixelDataPtr);
}


Als MemoryPool.AllocBuffer() een lock pakt, kan het zijn dat er op dat moment een OnPaint wordt uitgevoerd terwijl de pixelDataPtr naar een ongeldig of te klein stuk geheugen wijst.
Deze code gaat ook al fout als bijv. reader.NextWidth() een exception gooit en ziet er gevoelig voor buffer overflows uit. Ook heeft het weinig met managed code te maken. Wat je hier zou willen is gewoon iets als bitmap = reader.readBitmap(); En ja zo moet je overal zorgen dat de boel consistent blijft waar exceptions of reentrance mogelijk is. Dingen die bij elkaar horen tegelijkertijd updaten en groeperen in objecten zoals dat hoort enzo. :p
offtopic:
Bitmap heeft overigens ook nog een aparte constructor voor unmanaged pointers, maar dan blijft geheugenbeheer een probleem. Eigenlijk wil je in principe zo min mogelijk unmanagede onzin in een managed applicatie hebben.
Natuurlijk kun je dit dan weer omschrijven zodat het enigzins goed gaat, maar dit zit natuurlijk overal en vaak in componenten die gebruikt worden door andere componenten die weer mogelijk gebruikt worden vanaf de UI thread, iets wat ook een beetje door het MVVM pattern wordt uitgedragen.
Alles omschrijven dus... Of switchen naar unmanaged, maar ik denk niet dat het daar eenvoudiger op wordt. Dan nog blijft het probleem van inconsistente staat na exception bestaan.

Aangezien het probleem zich grotendeels lijkt te beperken tot OnPaint() en subevents, zal het in de praktijk nog wel meevallen. Ik zie niet zoveel mensen die dit probleem hebben opgemerkt in hun applicaties. (Hoewel dit natuurlijk typisch een lastig te debuggen probleem is.)

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • LeX-333
  • Registratie: Maart 2004
  • Laatst online: 21-11-2016
pedorus schreef op dinsdag 01 mei 2012 @ 23:47:
Deze code gaat ook al fout als bijv. reader.NextWidth() een exception gooit en ziet er gevoelig voor buffer overflows uit. Ook heeft het weinig met managed code te maken. Wat je hier zou willen is gewoon iets als bitmap = reader.readBitmap(); En ja zo moet je overal zorgen dat de boel consistent blijft waar exceptions of reentrance mogelijk is. Dingen die bij elkaar horen tegelijkertijd updaten en groeperen in objecten zoals dat hoort enzo. :p
Dit was natuurlijk maar een versimpeld voorbeeld, aangepast om het probleem aan te geven. Het punt dat ik wil maken is dat als je ook maar een beetje interessante rendering doet in de OnPaint het bijna niet meer mogelijk is om de hele boel reentrant safe te maken.
pedorus schreef op dinsdag 01 mei 2012 @ 23:47:
Aangezien het probleem zich grotendeels lijkt te beperken tot OnPaint() en subevents, zal het in de praktijk nog wel meevallen. Ik zie niet zoveel mensen die dit probleem hebben opgemerkt in hun applicaties. (Hoewel dit natuurlijk typisch een lastig te debuggen probleem is.)
De OnPaint is het verreweg het makkelijkst te reproduceren, echter heb ik ook twee methodes die met BeginInvoke gescheduled waren elkaar zien onderbreken en, zoals bijvoorbeeld in de TS, een OnPaint onderbroken zien worden door een mouse-click of een andere OnPaint. Dat komt alleen veel minder vaak voor, maar als het gebeurd is het natuurlijk absoluut niet te voorspellen wat er fout gaat.
pedorus schreef op dinsdag 01 mei 2012 @ 23:47:
Alles omschrijven dus... Of switchen naar unmanaged, maar ik denk niet dat het daar eenvoudiger op wordt. Dan nog blijft het probleem van inconsistente staat na exception bestaan.
Het begint er inderdaad op te lijken dat als je software wilt maken die gewoon altijd werkt, en niet iets wat in de meeste gevallen waarschijnlijk wel goed gaat, unmanaged inderdaad de enige echte oplossing is. Unmanaged heeft ook zo zijn nadelen, echter reentrancy is bijna niet te debuggen en is ook nagenoeg niet tegen te designen.

Too many people, making too many problems


Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 11-09 12:01
Welke componenten locken je UI thread dan? Is het een optie om alleen daar iets anders voor te zoeken of ze op een andere manier gebruiken?

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!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
Die BeginInvoke is inderdaad een vervelender probleem, OnPaints die falen valt met een try- catch en desnoods wat tekenfoutjes op de koop toenemen vrij makkelijk om heen te werken. Even een stacktrace:
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
    [In a sleep, wait, or join] 
>   WindowsFormsApplication4.exe!Test.Test.OnBeginInvoke() Line 69  C#
    WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate callback, object args, int numArgs) + 0xba bytes 
    WindowsBase.dll!MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(object source, System.Delegate method, object args, int numArgs, System.Delegate catchHandler) + 0x42 bytes    
    WindowsBase.dll!System.Windows.Threading.DispatcherOperation.InvokeImpl() + 0x8d bytes  
    WindowsBase.dll!System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(object state) + 0x38 bytes 
    mscorlib.dll!System.Threading.ExecutionContext.runTryCode(object userData) + 0x51 bytes 
    [Native to Managed Transition]  
    [Managed to Native Transition]  
    mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) + 0x6a bytes    
    mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool ignoreSyncCtx) + 0x7e bytes    
    mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) + 0x2c bytes    
    WindowsBase.dll!System.Windows.Threading.DispatcherOperation.Invoke() + 0x68 bytes  
    WindowsBase.dll!System.Windows.Threading.Dispatcher.ProcessQueue() + 0x15e bytes    
    WindowsBase.dll!System.Windows.Threading.Dispatcher.WndProcHook(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled) + 0x63 bytes 
    WindowsBase.dll!MS.Win32.HwndWrapper.WndProc(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled) + 0xbe bytes    
    WindowsBase.dll!MS.Win32.HwndSubclass.DispatcherCallbackOperation(object o) + 0x7d bytes    
    WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate callback, object args, int numArgs) + 0x53 bytes 
    WindowsBase.dll!MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(object source, System.Delegate method, object args, int numArgs, System.Delegate catchHandler) + 0x42 bytes    
    WindowsBase.dll!System.Windows.Threading.Dispatcher.InvokeImpl(System.Windows.Threading.DispatcherPriority priority, System.TimeSpan timeout, System.Delegate method, object args, int numArgs) + 0xb4 bytes    
    WindowsBase.dll!MS.Win32.HwndSubclass.SubclassWndProc(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam) + 0x104 bytes    
    [Native to Managed Transition]  
    [Managed to Native Transition]  
    System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.DefWndProc(ref System.Windows.Forms.Message m) + 0x6d bytes  
    System.Windows.Forms.dll!System.Windows.Forms.Form.DefWndProc(ref System.Windows.Forms.Message m) + 0x60 bytes  
    System.Windows.Forms.dll!System.Windows.Forms.Control.WndProc(ref System.Windows.Forms.Message m) + 0xe1 bytes  
    System.Windows.Forms.dll!System.Windows.Forms.ScrollableControl.WndProc(ref System.Windows.Forms.Message m) + 0x2a bytes    
    System.Windows.Forms.dll!System.Windows.Forms.Form.WndProc(ref System.Windows.Forms.Message m) + 0x5e bytes 
    System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.OnMessage(ref System.Windows.Forms.Message m) + 0x13 bytes    
    System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.WndProc(ref System.Windows.Forms.Message m) + 0x31 bytes  
    System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.DebuggableCallback(System.IntPtr hWnd, int msg, System.IntPtr wparam, System.IntPtr lparam) + 0x64 bytes 
    [Native to Managed Transition]  
    [Managed to Native Transition]  
    mscorlib.dll!System.Threading.Monitor.Enter(object obj, ref bool lockTaken) + 0x14 bytes    
    WindowsFormsApplication4.exe!Test.Test.OnPaint(System.Windows.Forms.PaintEventArgs e) Line 87 + 0x17 bytes  C#
    System.Windows.Forms.dll!System.Windows.Forms.Control.PaintWithErrorHandling(System.Windows.Forms.PaintEventArgs e, short layer) + 0xa1 bytes   
    System.Windows.Forms.dll!System.Windows.Forms.Control.WmPaint(ref System.Windows.Forms.Message m) + 0x3a5 bytes 
    System.Windows.Forms.dll!System.Windows.Forms.Control.WndProc(ref System.Windows.Forms.Message m) + 0x2b1 bytes 
    System.Windows.Forms.dll!System.Windows.Forms.ScrollableControl.WndProc(ref System.Windows.Forms.Message m) + 0x2a bytes    
    System.Windows.Forms.dll!System.Windows.Forms.Form.WndProc(ref System.Windows.Forms.Message m) + 0x5e bytes 
    System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.OnMessage(ref System.Windows.Forms.Message m) + 0x13 bytes    
    System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.WndProc(ref System.Windows.Forms.Message m) + 0x31 bytes  
    System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.DebuggableCallback(System.IntPtr hWnd, int msg, System.IntPtr wparam, System.IntPtr lparam) + 0x64 bytes 
    [Native to Managed Transition]  
    [Managed to Native Transition]  
    mscorlib.dll!System.Threading.Monitor.Enter(object obj, ref bool lockTaken) + 0x14 bytes    
    WindowsFormsApplication4.exe!Test.Test.OnButtonClick(object sender, System.EventArgs e) Line 55 + 0x17 bytes    C#
    System.Windows.Forms.dll!System.Windows.Forms.Control.OnClick(System.EventArgs e) + 0x7f bytes  
    System.Windows.Forms.dll!System.Windows.Forms.Button.OnClick(System.EventArgs e) + 0xa2 bytes   
    System.Windows.Forms.dll!System.Windows.Forms.Button.OnMouseUp(System.Windows.Forms.MouseEventArgs mevent) + 0xac bytes 
    System.Windows.Forms.dll!System.Windows.Forms.Control.WmMouseUp(ref System.Windows.Forms.Message m, System.Windows.Forms.MouseButtons button, int clicks) + 0x2d1 bytes 
    System.Windows.Forms.dll!System.Windows.Forms.Control.WndProc(ref System.Windows.Forms.Message m) + 0x93a bytes 
    System.Windows.Forms.dll!System.Windows.Forms.ButtonBase.WndProc(ref System.Windows.Forms.Message m) + 0x127 bytes  
    System.Windows.Forms.dll!System.Windows.Forms.Button.WndProc(ref System.Windows.Forms.Message m) + 0x20 bytes   
    System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.OnMessage(ref System.Windows.Forms.Message m) + 0x13 bytes    
    System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.WndProc(ref System.Windows.Forms.Message m) + 0x31 bytes  
    System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.DebuggableCallback(System.IntPtr hWnd, int msg, System.IntPtr wparam, System.IntPtr lparam) + 0x64 bytes 
    [Native to Managed Transition]  
    [Managed to Native Transition]  
    System.Windows.Forms.dll!System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(System.IntPtr dwComponentID, int reason, int pvLoopData) + 0x287 bytes    
    System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(int reason, System.Windows.Forms.ApplicationContext context) + 0x16c bytes  
    System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoop(int reason, System.Windows.Forms.ApplicationContext context) + 0x61 bytes    
    System.Windows.Forms.dll!System.Windows.Forms.Application.Run(System.Windows.Forms.Form mainForm) + 0x31 bytes  
    WindowsFormsApplication4.exe!Test.Test.Main() Line 14 + 0x1d bytes  C#
    [Native to Managed Transition]  
    [Managed to Native Transition]  
    mscorlib.dll!System.AppDomain.ExecuteAssembly(string assemblyFile, System.Security.Policy.Evidence assemblySecurity, string[] args) + 0x6d bytes    
    Microsoft.VisualStudio.HostingProcess.Utilities.dll!Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() + 0x2a bytes  
    mscorlib.dll!System.Threading.ThreadHelper.ThreadStart_Context(object state) + 0x63 bytes   
    mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool ignoreSyncCtx) + 0xb0 bytes    
    mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) + 0x2c bytes    
    mscorlib.dll!System.Threading.ThreadHelper.ThreadStart() + 0x44 bytes   
    [Native to Managed Transition]


Het lijkt erop dat er een extra message pump wordt gestart in een event dat wel mag in Monitor.Enter zijn message pump, en die message pump (voor het rechter-muisknop menu gelijk te tekenen ofzo) voert vervolgens willekeurige events (behalve interactieve zoals clicks) uit. Nasty...

Nou is dit helaas dus een mix met WPF en Forms, maar dat maakt niet zoveel uit. Als ik de onafhankelijke vorm kies (SynchronizationContext.Current.Post(_ => { OnBeginInvoke(); }, null);), dan werkt het helaas ook, dus met WPF heeft het niets te maken:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    [In a sleep, wait, or join] 
>   WindowsFormsApplication4.exe!Test.Test.OnBeginInvoke() Line 69  C#
    WindowsFormsApplication4.exe!Test.Test.OnButtonClick.AnonymousMethod__2(object _) Line 46 + 0x9 bytes   C#
    [Native to Managed Transition]  
    mscorlib.dll!System.Delegate.DynamicInvokeImpl(object[] args) + 0x77 bytes  
    System.Windows.Forms.dll!System.Windows.Forms.Control.InvokeMarshaledCallbackDo(System.Windows.Forms.Control.ThreadMethodEntry tme) + 0xa7 bytes    
    System.Windows.Forms.dll!System.Windows.Forms.Control.InvokeMarshaledCallbackHelper(object obj) + 0x69 bytes    
    mscorlib.dll!System.Threading.ExecutionContext.runTryCode(object userData) + 0x51 bytes 
    [Native to Managed Transition]  
    [Managed to Native Transition]  
    mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) + 0x6a bytes    
    mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool ignoreSyncCtx) + 0x7e bytes    
    mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) + 0x2c bytes    
    System.Windows.Forms.dll!System.Windows.Forms.Control.InvokeMarshaledCallback(System.Windows.Forms.Control.ThreadMethodEntry tme) + 0x95 bytes  
    System.Windows.Forms.dll!System.Windows.Forms.Control.InvokeMarshaledCallbacks() + 0x11f bytes  
    System.Windows.Forms.dll!System.Windows.Forms.Control.WndProc(ref System.Windows.Forms.Message m) + 0x120 bytes 
    System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.OnMessage(ref System.Windows.Forms.Message m) + 0x13 bytes    
    System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.WndProc(ref System.Windows.Forms.Message m) + 0x31 bytes  
    System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.DebuggableCallback(System.IntPtr hWnd, int msg, System.IntPtr wparam, System.IntPtr lparam) + 0x64 bytes 
    [See above, line 22]
LeX-333 schreef op woensdag 02 mei 2012 @ 22:12:
Het begint er inderdaad op te lijken dat als je software wilt maken die gewoon altijd werkt, en niet iets wat in de meeste gevallen waarschijnlijk wel goed gaat, unmanaged inderdaad de enige echte oplossing is. Unmanaged heeft ook zo zijn nadelen, echter reentrancy is bijna niet te debuggen en is ook nagenoeg niet tegen te designen.
Er zijn maar heel weinig mensen die unmanaged code goed kunnen schrijven (zie de vele security bugs). De code die je liet zien doet denken dat je gewent bent om unmanaged code te schrijven, maar de kans op fouten wordt voor normale programmeurs daar zeker niet kleiner door.

In ieder geval weer iets geleerd om rekening mee te houden... Gelukkig doe ik niet veel met GUIs in .net ;)

Nog iets gevonden:
MSDN: Does SpinLock.Enter do managed waiting (and pumping) while it waits for the lock?
(en gelinkte MSDN: Threading Model )

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten

Pagina: 1