Laden...

[erledigt] Compact Framework und Multithreading - volatile Keyword

Erstellt von inflames2k vor 12 Jahren Letzter Beitrag vor 12 Jahren 2.801 Views
inflames2k Themenstarter:in
2.298 Beiträge seit 2010
vor 12 Jahren
[erledigt] Compact Framework und Multithreading - volatile Keyword

Hallo,

zur Erweiterung einer Anwendung um ein Benachrichtigungssystem habe ich eine allgemeine Klassenbibliothek ausprogrammiert. Nach dem ich alles durchprogrammiert hatte, habe ich zum Test eine Windows Forms Anwendung erstellt, die auf die Funktionalitäten der Bibliothek zugreift und diese nutzt.

Alles funktionierte einwandfrei. Anschließend habe ich eine Bibliothek mit den selben Code-Dateien für das Compact-Framework erstellt, da die Verweise auf Framework Assemblies dort anders sind.

Erstaunt musste ich jedoch feststellen, dass sich die Anwendung ganz anders verhält als erwartet und der boolsche Wert nur dem entspricht, was innerhalb des Threads gesetzt wurde.

Hier hab ich dann auf das volatile Keyword zurückgegriffen, da es ja meines Wissens nach genau für den Fall dass aus mehreren Threads gesetzt werden soll da ist.

Aber dies brachte keinen Erfolg.

Anschließend habe ich den Verweis in meiner Testanwendung entfernt und die CF Bibliothek dort eingebunden. Und wieder war ich verblüfft, in der Windows Forms Anwendung kam es damit zu den genau richtigen Ergebnissen.

Was muss ich nun aber im Compact-Framework tun?!

Abgespeckt sieht mein Code in der entsprechenden Klasse wie folgt aus:


public class Notifier
{
    private volatile bool _bNotificationVisible = false;
    private Notification _currentNotification == null;

    public void Start() { /* some code */ }
    public void Stop() { /* some code */ }

    public void Deny()
    {
        / * some code to deny */

        _bNotificationVisible = false;
    }

   public void Accept()
   {
       /* some code to accept */
 
      _bNotificationVisible = false;
   }

   ///
   // The thread method ... runs all the time
   private void DoWork()
   {
        do
        {
              // get notifications

              // remove old notifications

             // add new notifications

             if(!_bNotificationVisible && /* some properties of Notification are true */)  
             {         
                   / * code for show next notification 
                        an event will be thrown...
                   */
                   _bNotificationVisible = true;
             }

             Thread.Sleep(/* some duration */);
        }while(_workerThread == Thread.CurrentThread);
   }
}

Aus oben dargestelltem müsste ersichtlich sein, wie das ganze vom Prinzip her funktionieren soll.

Nun habe ich aber folgendes Problem:
Wird eine Meldung angezeigt und anschließend die Accept Methode aufgerufen, wird wie auch gewünscht unter .NET sowie .NET compact _bNotificationVisible auf false gesetzt. Innerhalb des Threads wird dies anschließend auch als false gehandlet und die nächste Meldung ausgegeben.

Die Methode Deny liefert allerdings ein ganz anderes Ergebnis, unter dem großen Framework wird wie auch erwartet _bNotificationVisible auf false gesetzt und anschließend die nachfolgende Meldung ausgegeben.
Unterm Compact Framework allerdings wird _bNotificationVisible zuerst innerhalb der Methode auf false gesetzt, im nächsten Durchlauf der Threadmethode allerdings ist der Wert wieder true, wird allerdings nur an der einen Stelle auf true gesetzt die zwischenzeitlich nicht erreicht wurde.

Ich hoffe ihr könnt mir denkanstöße geben, woran dies liegen könnte. Wahrscheinlich ist es so trivial, dass ich meinen Kopf dannach als Hammer zum Nägel in die Wand klopfen nehmen sollte.

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |

6.911 Beiträge seit 2009
vor 12 Jahren

Hallo inflames2k,

wenn volatile beim CF nicht greift kannst du auch Interlocked* verwenden (das gibts hoffentlich beim CF).

* aber dann für alle Zugriffe auf das gemeinsame Feld.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

49.485 Beiträge seit 2005
vor 12 Jahren

Hallo inflames2k,

trivial ist Multithreading fast nie. Und volatile auch nicht. Siehe volatile durch lock ersetzen. Insbesondere kann man sich nicht darauf verlassen, dass der Wert, der von einem Thread in eine Variable geschrieben wird, rechtzeitig in dem anderen Thread "ankommt", der die Variable ausliest.

Was mich hier stutzig macht, ist dass es mit der Accept-Methode gehen soll, aber mit der Deny-Methode nicht, obwohl beide die gleiche Variable auf die gleiche Weise setzen. Ich vermute daher, dass die Unterschiede nicht durch das Setzen der Variable verursacht werden, sondern durch die weiteren Aktionen, die die Methoden durchführen, zumal bei dem if ja auch noch weitere Bedingungen berücksichtigt werden.

Ansonsten ist es nichts besonderes, wenn ein (fehlerhaftes) Multithreading-Programm in der einen Umgebung einen Fehler produziert, in einer anderen aber nicht (nicht nur, dass Synchronisationskonstrukte auf unterschiedlichen Plattformen unterschiedlich implementiert sind, sondern es gibt ja auch noch Race Conditions). Das bedeutet nicht automatisch, dass volatile nicht oder nicht richtig funktioniert.

herbivore

5.742 Beiträge seit 2007
vor 12 Jahren

Hallo inflames2k,

mir sieht das nach einem nachgebauten Timer aus, oder? 😉

Verwende lieber einen derartigen und entsprechende Invokes (siehe [FAQ] Controls von Thread aktualisieren lassen (Control.Invoke/Dispatcher.Invoke))

inflames2k Themenstarter:in
2.298 Beiträge seit 2010
vor 12 Jahren

wenn volatile beim CF nicht greift kannst du auch Interlocked* verwenden (das gibts hoffentlich beim CF).

Werd ich mir einmal anschauen, aber wahrscheinlich reicht das im von herbivore verlinkten Thread beschriebene vorgehen mit lock vollkommen aus. - Ich wollte anfangs das boolsche Feld auch in allen Zugriffen locken, später ging mir jedoch durch den Kopf, dass volatile eigentlich fast allein reichen sollte.

Ich denke ich werde beides in Kombination verwenden.

mir sieht das nach einem nachgebauten Timer aus, oder? Augenzwinkern

Nein, dass siehst du falsch. 😃

Grundlegend beschrieben ohne ins detail zu gehen:
Ein externes System schreibt Meldungen in eine Datenbank. Der Kunde wünscht nun, dass bei bestimmten Meldungen eine Nachricht in verschiedenen Anwendungen aufgeht. Wird nun aber an einer (egal welcher) diese Nachricht quittiert, soll diese in allen Anwendungen ausgeblendet werden. - Wird die Nachricht abgelehnt / ausgeblendet, so soll diese nur in der jeweiligen Anwendung nicht mehr angezeigt werden. Ich hoffe du verstehst die Ausführungen. 😃

@herbivore:
Ich wollte damit auch nicht ausdrücken, dass Multithreading trivial ist. 😃 Ich bezog mich eher darauf, dass mein denkfehler sich in dem Bereich befindet.

Insbesondere kann man sich nicht darauf verlassen, dass der Wert, der von einem Thread in eine Variable geschrieben wird, rechtzeitig in dem anderen Thread "ankommt", der die Variable ausliest.

Hier hast du natürlich recht, aber spätestens nach 5 Minuten sollte der Thread doch den aktuellen Wert haben?

Was mich hier stutzig macht, ist dass es mit der Accept-Methode gehen soll, aber mit der Deny-Methode nicht, obwohl beide die gleiche Variable auf die gleiche Weise setzen. Ich vermute daher, dass die Unterschiede nicht durch das Setzen der Variable verursacht werden, sondern durch die weiteren Aktionen, die die Methoden durchführen, zumal bei dem if ja auch noch weitere Bedingungen berücksichtigt werden.

Naja, die letzte Aktion in beiden Methoden ist das false setzen der Variable die letzte Aktion. - Einziger Unterschied ist, dass bei der Accept Methode noch ein Aufruf eines Webservices zuvor durchgeführt wird um die Quittierung der Datenbank bekannt zu machen. Bei der Deny Methode wird lediglich die Denied Property der Meldung auf true gesetzt, um zu verhindern, dass abgelehnte Meldungen erneut angezeigt werden.
Jetzt wo du es sagst, fällt mir auch der Unterschied zwischen Accept und Deny ein. Aufgrund des Webservice Aufrufs, der vorraussetzt, dass die aktuelle Meldung bekannt ist, habe ich ein lock um die Zuweisungen und den Webservice Aufruf.

Ich werde es Morgen also mit dem locking an allen 3 Stellen probieren.

Ansonsten danke ich euch schon einmal für die Hilfe und werde morgen noch eine Information liefern, ob es daran lag.

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |

inflames2k Themenstarter:in
2.298 Beiträge seit 2010
vor 12 Jahren

Hm irgendwie wird das nichts. Egal was ich mache, bei der Deny Methode bleibt es wie gehabt und der Wert wird kurz auf false gesetzt ist anschließend aber direkt wieder true.

Das volatile Schlüsselwort habe ich nun ersteinmal entfernt. Und auch die locks habe ich entsprechend ersteinmal wieder herausgenommen.

Ich seh nur eben nicht, wo der Unterschied zwischen den beiden Methoden ist.

Meine Accept Methode im ganzen:


public bool AcceptNotification(UserData user
{
	string sError = string.Empty;
	bool bSuccess = false;
			
	Notification innerNotification = _currentNotification.Notification;
	bSuccess = this._client.AcceptNotification(user, out sError);
	if (!bSuccess)
	{
		this.OnErrorOccured(sError);
	}
	else
	{
		_currentNotification.Accepted = true;
		this._bNotificationVisible = false;				}			

	return bSuccess;
}

Und hier die Deny Methode. Ich seh einfach den Grund nicht, warums die eine Methode tut, die andere aber nicht.


public void DenyNotification()
{
	_bNotificationVisible = false;	
	_currentNotification.Denied = true;		
}

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |

49.485 Beiträge seit 2005
vor 12 Jahren

Hallo inflames2k,

klingt nach einem Fall für [Tutorial] Vertrackte Fehler durch Vergleich von echtem Projekt mit minimalem Testprojekt finden, in dem Fall für zwei Methoden statt zwei Projekten.

Wobei

der Wert wird kurz auf false gesetzt ist anschließend aber direkt wieder true.

nun nicht danach klingt, als wäre das ein volatile-Problem sondern als wäre der Wert tatsächlich an einer anderen Stelle wieder auf true gesetzt.

herbivore

916 Beiträge seit 2008
vor 12 Jahren

Hi inflames2k,

ich würde mal folgendes tun, da ich auch der Meinung bin dass das Flag von irgendeiner anderen Instanz zurück gesetzt wird. Mach mal das Flag privat und definiere dafür eine Property. Dann ersetze alle Zuweisungen und benutze die Property und dann debug mal. Eventuell findest du den "Fehler" dann leichter, weil du so leicht mit bekommen würdest was im Workflow nicht stimmt.

Grüße rollerfreak2

Again what learned...

49.485 Beiträge seit 2005
vor 12 Jahren

Hallo inflames2k,

apropos Instanz, benutze beiden bzw. alle Threads überhaupt dieselbe Instanz, in der _bNotificationVisible enthalten ist?

herbivore

inflames2k Themenstarter:in
2.298 Beiträge seit 2010
vor 12 Jahren

@herbivore:

Das boolsche Feld wird nur an genau einer Stelle auf true gesetzt. An dieser habe ich einen Haltepunkt, der nach dem Aufruf von Deny nie wieder angesprungen wird.

Ablauf ist ja wie folgt: Zu Programmbeginn ist der Wert false. - Daher wird die erste Meldung auf jeden Fall angezeigt. Akzeptiere ich wird der Wert in Accept auf false gesetzt. Ein Haltepunkt auf die if() ob der Wert false ist habe ich auch gesetzt. Nach Accept ist der Wert auch tatsächlich false.
Lehne ich mittels Deny ab, wird der Wert auf false gesetzt. - Im Debugger steht er auch auf false. Anschließend wird der Haltepunkt bei der if angesprungen und der Wert ist true. -> Dabei wurde aber die Stelle die true setzt nie aufgerufen.

Und zu deinem zweiten Post: Ja, es wird die selbe Instanz genutzt, da es nur eine Instanz der Klasse gibt.

Ich werde dann mal ein Testprojekt erstellen.

@Rollerfreak:
Das boolsche Feld wird nur innerhalb der Klasse gesetzt und ist private.

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |

inflames2k Themenstarter:in
2.298 Beiträge seit 2010
vor 12 Jahren

Ich hab es nun auf andere Art gelöst, dabei brauche ich weder locks noch volatile.

Das Field _bNotificationVisible wird garnicht mehr verwendet. Stattdessen greife ich nur auf Notification.Accepted & Notification.Denied zu.

Auf allen 3 Handscannern, sowie der Windows Oberfläche funktioniert dies wie gewünscht.

Dennoch danke ich euch für eure Antworten, da ich wohl selbst erst sehr spät auf die Idee eines eigenständigen Testprojektes gekommen wäre.

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |