Laden...

SyncQueue<T> Monitor.Pulse gibt es nicht in Compact Framework

Erstellt von Kostas vor 13 Jahren Letzter Beitrag vor 13 Jahren 7.500 Views
Hinweis von herbivore vor 13 Jahren

Abgeteilt von SyncQueue <T> - Eine praktische Job-Queue

K
Kostas Themenstarter:in
597 Beiträge seit 2005
vor 13 Jahren
SyncQueue<T> Monitor.Pulse gibt es nicht in Compact Framework

Hallo herbivore,

ich würde gerne die SyncQueue<T> verwenden.
Doch leider gibt es Monitor.Pulse und Monitor.Wait nicht im CF.

Hast du eine Idee?

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo Kostas,

nicht für alles, was im CF fehlt, gibt es einen Ersatz oder Workaround. Manches was mit dem vollen Framework geht, geht mit dem CF einfach nicht. Ich habe jedenfalls nichts parat. Aber vielleicht fällt ja jemand anders noch was ein.

herbivore

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo,

mit ManualResetEvent kann dies für das CF nachgebaut werden. Siehe Blocking queues (in den Kommentaren).

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!"

1.361 Beiträge seit 2007
vor 13 Jahren

Hi,

ich hätte auch noch einen Vorschlag:

public class SyncQueue <T>
{
  //--------------------------------------------------------------------------
  private Queue <T> _q   = new Queue <T> ();
  // <NEW>  
  private AutoResetEvent _evt;
  // </NEW>

  //==========================================================================
  // <summary>Trägt einen Eintrag (ohne zu warten) ein</summary>
  public void Enqueue (T tItem)
  {
    lock (this) {
      _q.Enqueue (tItem);

      // <OLD>
      //Monitor.Pulse(this);
      // </OLD>

      // <NEW>
      _evt.Set();
      // </NEW>

      Debug.WriteLine (Thread.CurrentThread.Name + " Enqueue ==> " + _q.Count);
    }
  }

  //==========================================================================
  // <summary>
  //    Holt einen Eintrag aus der Queue heraus und wartet dabei nötigenfalls
  //    solange bis wieder ein Eintrag vorhanden ist.
  // </summary>
  public T Dequeue ()
  {
    lock (this) {
      while (_q.Count == 0) {
        Debug.WriteLine (Thread.CurrentThread.Name + " Wait");

        // <OLD>
        //Monitor.Wait (this);
        // </OLD>

        // <NEW>     
        _evt.Reset();
        Monitor.Exit(this);
        _evt.WaitOne();
        Monitor.Enter(this);
        // </NEW>

      }
      Debug.WriteLine (Thread.CurrentThread.Name + " Dequeue ==> " + (_q.Count - 1));
      return _q.Dequeue ();
    }
  }
}

Was sagt ihr? sollte doch gehen, oder?

beste Grüße
zommi

K
Kostas Themenstarter:in
597 Beiträge seit 2005
vor 13 Jahren

Hallo Zusammen,

ich habe es mal so versucht, indem ich
Dequeue und DoWorkAsync wie folgt abgeändert habe.
In meinem Fall, gibt es nur genau einen Producer und einen Consumer Thread.

Kann ich das so lassen?
Gruß Kostas

        public T Dequeue()
        {
            lock (this)
            {
//                while (_q.Count == 0)
                {
                    Debug.WriteLine(Thread.CurrentThread.Name + " Wait");
                    //Monitor.Wait(this);
                }
                Debug.WriteLine(String.Format("{0} Dequeue ==> {1}", Thread.CurrentThread.Name, (_q.Count - 1)));
                return _q.Dequeue();
            }
        }
        public void DoWorkAsync()
        {
            _exit = false;
            // Ab .net 4.0 besser Task.Factory.StartNew verwenden.
            ThreadPool.QueueUserWorkItem(_ =>
            {
                while (!_exit)
                {
                    try
                    {
                        if (ThreadHelper._queue.Count() > 0)
                        {
                            string aLine = ThreadHelper._queue.Dequeue();

                                    this.Progress.Fire(this, new ProgressEventArgs(aLine));
                            }
                        }
                        else Thread.Sleep(500);

                    }
                    catch (Exception ex)
                    {

                        this.Error.Fire(this, new ErrorEventArgs(ex.Message);
                    }


                }

                this.Finished.Fire(this);
            });

        }
49.485 Beiträge seit 2005
vor 13 Jahren

Hallo zommi,

AutoResetEvent passt nicht, weil Signalisierungen verloren gehen können. Siehe Thread-Synchronisation mit mehreren Consumer-Threads.

Hallo gfoidl,

wie genau wäre der Vorschlag mit ManualResetEvent? Ich fürchte, wenn man nicht sehr genau aufpasst, haut das auch nicht hin.

Hallo Kostas,

naja, blockierend arbeitet dein Code nicht, sondern mit Polling. Kann man machen, aber mit SyncQueue hat das dann nichts mehr zu tun.

herbivore

K
Kostas Themenstarter:in
597 Beiträge seit 2005
vor 13 Jahren

naja, blockierend arbeitet dein Code nicht, sondern mit Polling. Kann man machen, aber mit SyncQueue hat das dann nichts mehr zu tun.

In meinem Fall, gibt es nur genau einen Producer und einen Consumer Thread.
Ist es dan in Ordung es so zu machen?
Was mit garnich gefällt ist Thread.Sleep(500);

Gruß Kostas

1.361 Beiträge seit 2007
vor 13 Jahren

AutoResetEvent passt nicht, weil Signalisierungen verloren gehen können. Siehe
>
.

Genau wie bei Monitor.Pulse(...)! 😃
Deshalb pulset du ja auch bei jedem Enqueue und im Dequeue wartest du auch nur, wenn kein Element drin ist.

Ich habe mit dem AutoResetEvent ja nur das MonitorPulse-Verhalten nachgebaut.

Insofern sollte es also schon gehen - genau wie dein Code ja auch ging.
beste Grüße
zommi

PS:
Ich antwort mal für gfoidl auf

wie genau wäre der Vorschlag mit ManualResetEvent? Ich fürchte, wenn man nicht sehr genau aufpasst, haut das auch nicht hin.

Dort bauen sie mit ManualResetEvents eine Semaphore nach. Und die kann man natürlich auch zur Lösung des Consumer-Producer-Problems verwenden.

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo zommi,

Genau wie bei Monitor.Pulse(...)!

nein, leider nicht wie bei Monitor.Pulse. Bei Pulse gehen Signale nur "verloren", wenn kein Thread im Wait ist. Bei ARE können können Signale auch verloren gehen, wenn (mehrere) Threads im Wait sind. Insofern bin ich weiterhin der Meinung, dass es Fälle gibt, in denen die Queue nicht korrekt funktioniert.

Hallo Kostas,

In meinem Fall, gibt es nur genau einen Producer und einen Consumer Thread.

das vereinfacht die Synchronisierung deutlich (eine nicht blockierende Queue bekommt man für diesen Fall sogar ganz ohne Synchronisierung hin).

Ist es dan in Ordung es so zu machen?

Wie schon sagte:

Kann man machen, aber mit SyncQueue hat das dann nichts mehr zu tun.

herbivore

1.361 Beiträge seit 2007
vor 13 Jahren

Bei ARE können können Signale auch verloren gehen, wenn (mehrere) Threads im Wait sind

Aaah, ok, jetzt versteh ich was du meinst. Und ja, das kann hier zu Probleme führen.

Hab das grad mal durch ein Test nachgestellt.
Demnach führt ein (ManualReset|AutoReset)Event.Set(...) nicht sofort und unmittelbar zu einem Aufwachen eines wartenden Threads. Das Aufwecken scheint asynchron zum Signalisieren zu laufen.
Wenn man schnell genug ist, kann man mit einem direkt nach dem Set(...) folgenden Reset(...) das Aufwachen soger verhindern, als hätte es nie ein Set gegeben!

Herbivore, hast du irgendwo eine Literatur-Quelle für das von dir beschriebene Verhalten? Habe das Verhalten bisher noch nicht dokumentiert gefunden.

beste Grüße
zommi

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo zommi,

Demnach führt ein (ManualReset|AutoReset)Event.Set(...) nicht sofort und unmittelbar zu einem Aufwachen eines wartenden Threads. Das Aufwecken scheint asynchron zum Signalisieren zu laufen.

genau!

Wenn man schnell genug ist, kann man mit einem direkt nach dem Set(...) folgenden Reset(...) das Aufwachen soger verhindern, als hätte es nie ein Set gegeben!

Korrekt!

hast du irgendwo eine Literatur-Quelle für das von dir beschriebene Verhalten?

Ich weiß leider nicht mehr, woher ich das habe. Aber das Verhalten ist eindeutig so, wie du es beschrieben hast.

Deshalb ist Set eben auch keine echte Alternative zu Pulse. Ein weiterer Unterschied ist, dass Monitor.Pulse und Monitor.Wait beide innerhalb des kritischen Bereichs angestoßen werden können (und auch müssen), wogegen man für ein EventHandle.WaitOne den kritischen Bereich zuerst verlassen muss. Nach dem Verlassen und vor den WaitOne kann also noch ein anderer Thread dazwischen hauen. Das erhöht die Schwierigkeiten noch. Deshalb meine Skepsis, dass man mit AutoResetEvent.Set Monitor.Pulse ersetzen kann.

herbivore

S
8.746 Beiträge seit 2005
vor 13 Jahren

OpenNETCF hat mit der Klasse Monitor2 eine Reimplementierung im Angebot.

A
69 Beiträge seit 2010
vor 13 Jahren

bei der OpenNETCF klasse muss man aber aufpassen, das es nicht zu verhungernden Threads kommt, da dort nicht sichergestellt ist, das die threads wie im .net Framework in einer festgelegten Reihenfolge aufgerufen werden.

edit: und wenn man die Sourcen des OpenNETCF Frameworks betrachtet, so entdeckt man, das die Monitor Klasse mit dem genannten AutoresetEvent arbeitet....

1.361 Beiträge seit 2007
vor 13 Jahren

OpenNETCF hat mit der Klasse Monitor2 eine Reimplementierung im Angebot.

Jup, aber sie leidet unter genau den selben Problemen wie meine obige Implementierung mit dem AutoResetEvent.
Vielleicht ist ja das Event-Systen in Windows CE völlig anders implementiert und verhält sich total anders, aber wenn nicht, dann ist die OpenNETCF-Implementierung falsch.

Hier mal der Test der Monitor2-Klasse:

using System;
using System.Threading;
using OpenNETCF.Threading;

class Test
{
	static Monitor2 monitor = new Monitor2();

	public static void Main()
	{		
		// wartende Threads erzeugen
		for(int i=0; i<10; i++)
			new Thread(WaitForPulse).Start();
		
		// sicherstellen, dass alle oben sind
		Thread.Sleep(1000);
			
		// alle aufwecken
		Console.WriteLine(">> PulseAll <<");		
		monitor.Enter();
		{
			monitor.PulseAll();
		}
		monitor.Exit();
	}
	
	static void WaitForPulse()
	{
		monitor.Enter();
		{
			Console.WriteLine("Waiting...");
			monitor.Wait();
			Console.WriteLine("Woke Up...");
		}
		monitor.Exit();
	}
}

Raus kommt bei mir leider: (8x geht das Set(...) bei mir verloren)

Waiting...
Waiting...
Waiting...
Waiting...
Waiting...
Waiting...
Waiting...
Waiting...
Waiting...
Waiting...
>> PulseAll <<
Woke Up...
Woke Up...

beste Grüße
zommi

1.361 Beiträge seit 2007
vor 13 Jahren

Eine andere Implementierung mittels Semaphoren sollte natürlich auch gehen.
Zwar gibt es im CompactFramework auch keine Semaphore, aber die von OpenNETCF schaut gut aus.

Der SyncQueue-Code ergibt sich dann aus:

using System;
using System.Collections.Generic;
using System.Threading;

public class SyncQueue <T>
{
   private Queue <T> _q   = new Queue <T> ();
   private Semaphore _s	  = new Semaphore(0, Int32.MaxValue);

   public void Enqueue (T tItem)
   {
      lock (this) {
         _q.Enqueue (tItem);
      }
	  _s.Release();
   }

   public T Dequeue ()
   {
      _s.WaitOne();
      lock (this) {
         return _q.Dequeue ();
      }
   }
}

Auf nem Desktop-.NET-System sollte aber herbivores Variante schneller sein, da die Monitor-Klasse zu großen Teilen im UserMode implementiert ist und dadurch nicht jedesmal ein Kernel-Trap nötig ist. Allerdings gibt es seit .NET 4.0 noch die SemaphoreSlim-Klasse, die wiederum leichtgewichtiger ist und mit herbivores Variante gleichziehen sollte.

beste Grüße
zommi

Gelöschter Account
vor 13 Jahren

evtl solle jemand die von OpenNETCF aufklären?

1.361 Beiträge seit 2007
vor 13 Jahren

Ich hab vorhin nen BugReport in deren bugzilla-System gemacht.

S
357 Beiträge seit 2007
vor 13 Jahren

@zommi Monitor2 : habs mal eben auf einem winCe5 ausprobiert und funktioniert richtig:

Waiting...
Waiting...
Waiting...
Waiting...
Waiting...
Waiting...
Waiting...
Waiting...
Waiting...
Waiting...
>> PulseAll <<
Woke Up...
Woke Up...
Woke Up...
Woke Up...
Woke Up...
Woke Up...
Woke Up...
Woke Up...
Woke Up...
Woke Up...

aber auch die hilfe sagt bereits einiges : No guarantee i made about which thread is woken.

1.361 Beiträge seit 2007
vor 13 Jahren

Hi snupi,

ich kenn mich leider mit dem WinCE System nicht aus, auch über die Implementierung der Synchronisationsprimitiven im Kernel kann ich nichst sagen.

Nur in der "normalen" Windows NT Welt hat ein AutoReset-Event ein spezifisches Verhalten, was auf einem Desktop-System dazu führt, dass die OpenNETCF-Implementierung fehlerhaft läuft.

Es kann sein, dass das AutoResetEvent unter WinCE anders implementiert ist und ein anderes Verhalten besitzt. Dazu hatte ich damals aber nichts finden können.

Genau betrachtet ist dein einmaliges Beobachten eines nicht fehlerbehafteten Durchlaufs jedoch kein Beweis für die Korrektheit der Implementierung! Bei dir trat der Fehler nur (noch) nicht auf. So ist das bei Threading/Synchronisations-Problemen immer. Aber dennoch könnte es sein, dass er wirklich nicht auftreten kann, wegen oben beschriebener WinCE-Besonderheiten, aber das kannst du mit einem Test-Run nicht einfach beweisen.

beste Grüße
zommi

PS: Auf meinen gemeldeten Bug 442 gabs nie eine Antwort, geschweige denn, dass er angenommen und gefixed wurde.

S
357 Beiträge seit 2007
vor 13 Jahren

dachte du hattest den fehler auf einer CE-platform.