Laden...

Methodenaufrufe mit Rückgabewert in Queue einreihen

Erstellt von Mossos vor 11 Jahren Letzter Beitrag vor 11 Jahren 1.811 Views
M
Mossos Themenstarter:in
47 Beiträge seit 2011
vor 11 Jahren
Methodenaufrufe mit Rückgabewert in Queue einreihen

Hallo zusammen,

ich hab folgendes Problem:
Verschiedene Objekte sollen Nachrichten über das Netzwerk an einen bestimmten Endpunkt senden und bekommen auch wieder eine Antwort zurück. Allerdings sollen die Nachrichten nacheinander und zwar in bestimmten Mindestzeitabständen gesendet werden, z.B. zwischen zwei Nachrichten müssen immer min. 2 Sekunden liegen.

Ich hatte mir jetzt gedacht, dass ich die Methoden in einer Queue einreihe und die dann nacheinander abarbeite, habe aber noch keinen vernünftigen Ansatz gefunden das zu realisieren. Das Problem war immer den Rückgabewert an die aufrufende Methode zurückzuschicken.

Ich hab z.B. versucht die Methoden (die die Nachrichten senden) in Delegates zu packen und die dann in eine Queue einzureihen. Permanent würde dann ein Thread die Queue abfragen, ob Nachrichten zu senden sind und wenn ja die Methoden dann nacheinander ausführen. Aber wie gesagt, habe ich keine Ahnung wie ich dann den Rückgabewert an das Objekt zurückzuschickensoll, welches die jeweilige Methode aufgerufen hat.

Hat da jemand vielleicht nen vernünftigen Ansatz?

6.911 Beiträge seit 2009
vor 11 Jahren

Hallo Mossos,

steck nicht die Methoden in die Queue, sondern ein Objekt, welches die Methode und den Rückgabewert besitzt.
In der aufrufenden Methode geschieht das ohnehin asynchron und somit kannst du dazu gleich die Task<T>-Klasse verwenden. Das Ergebnis setzt du dort mittels TaskCompletionSource und die Instanz von Task<T> gibts du an die aufrufende Methode zurück.

z.B. zwischen zwei Nachrichten müssen immer min. 2 Sekunden liegen.

Da du weiter unten von Thread geschrieben hast, hoffe ich dass du eh einen Timer dazu verwendest.

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 11 Jahren

Hallo Mossos,

als Queue kannst du dir mal SyncQueue <T> - Eine praktische Job-Queue anschauen. Da entfällt dann auch das permanente Abfragen, denn die Queue blockiert automatisch, wenn keine Nachricht enthalten ist und der Thread somit nichts zu tun hat. Mit einem einfachen Thread.Sleep in der Verarbeitungsschleife könntest du die Mindestzeit sicherstellen. Das wäre einer der wenigen Fälle, in denen man Thread.Sleep mal sinnvoll einsetzen kann.

In die Queue steckst du - wie von gfoidl gesagt - besser Objekte. Diese können dann statt oder zusätzlich zu einer Property für den Rückgabewert ein eigenes Event enthalten (siehe [FAQ] Eigenen Event definieren), das einfach am Ende der auszuführenden Methode gefeuert wird. Den Rückgabewert kannst du in den (eigenen) EventArgs übergeben oder über die schon genannte Property abrufen lassen.

herbivore

M
Mossos Themenstarter:in
47 Beiträge seit 2011
vor 11 Jahren

Hm gibts zur Task Klasse auch eine Alternative? Auf dem Server, auf dem die Anwendung laufen wird ist nur .Net 3.5 installiert.

Zum Timer: Was spricht eigentlich dagegen, den Thread nach jedem Methodenaufruf für die gewisse Zeit schlafen zu legen? Beim Timer müsste ich das doch auch machen, weil ich nicht will, dass er nur z.B. alle 2 Sekunden überprüft, ob eine Methode ausgeführt werden soll. Das sollte schon schneller gehen. (Die Methoden werden nicht kontinuierlich ausgeführt)

49.485 Beiträge seit 2005
vor 11 Jahren

Hallo Mossos,

meine vorige Antwort beantwortet auch schon alle deine Nachfragen.

herbivore

M
Mossos Themenstarter:in
47 Beiträge seit 2011
vor 11 Jahren

Oh sorry herbivore, hab deinen Post übersehen ^^.
SyncQueue hatte ich mir schon angeguckt, sieht gut aus. Aber ich weiß nicht ob das so mit den Objekten funktioniert.
Also ich muss dazu sagen ich lasse einen Webservice laufen. Der Benutzer geht auf einen Link, was im Code eine Methode aufruft, die dann eine Nachricht sendet und auf eine Antwort wartet. Die Antwort ist dann der Rückgabewert der Methode. Das heißt ich müsste in der Methode auf das Ergebnis warten. Mit Events geht das ja schlecht.

EDIT: Oh ich denke das müsste mit WaitOne() funktionieren oder?

6.911 Beiträge seit 2009
vor 11 Jahren

Hallo Mossos,

die Eingangsfrage in Zusammenhang mit dem Webservice macht für mich keinen Sinn. Entweder du versuchst hier etwas umständlicher als nötig zu machen od. ich verstehe ich dein Vorhaben nicht richtig. Kannst du das genauer beschreiben?

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

M
Mossos Themenstarter:in
47 Beiträge seit 2011
vor 11 Jahren

Ja das ganze Projekt ist auch recht umfangreich.
Vom Webservice trudeln halt immer wieder Nachrichten ein, die der Server an einen anderen Server weiterleiten muss (mit den Mindestabständen). Die Antwort des Server muss dann wieder vom Server zurück an den Webservice gegeben werden.

Ich habs aber jetzt gelöst bekommen.
Wie du gesagt hast, reihe ich jetzt die Objekte mit der Nachricht in die Queue ein. Den Objekten übergebe ich ein AutoResetEvent im Konstruktor und warte in der Methode des WebServices auf das Event (mit WaitOne).

Ein separater Thread überwacht die Queue, und sendet nach der Reihe die jeweilige Nachricht in den Objekten. Die Antwort des Servers wird in ein Resultattribut des Objektes zurückgegeben und gleichzeitig das Event ausgelöst. Anschließend wird der Thread für ne kurze Zeit schlafen gelegt.


//Die Methode im WebService
public Result ExecuteCommand(string command)
{
   AutoResetEvent ev = new AutoResetEvent(false);
   Message m = new Message(command, ev);
   messageQueue.EnqueueMessage(m);

   ev.WaitOne();
   return m.Result;
}

//Message-Klasse
....
public void ApplyResult(string result)
{
     this.result = result;
     //Löse event aus
     AutoEvent.Set();
 }

//MessageQueue-Klasse
....
public void enqueueMessage(Message m) { ... }

//Wird im separatem Thread ausgeführt
public void monitorQueue()
{
    while (currentState == State.running)
    {
          if (queue.Count > 0)
          {
                Message m = queue.Dequeue();
               
                //SendMessage schickt die Nachricht an den "End-Server" und gibt die Antwort zurück
                string result = SendMessage(m.Message);
                m.ApplyResult(result);

                //Der Mindeszeitabstand zwischen 2 Nachrichten
                Thread.Sleep(2000);
          } else
          {
                Thread.Sleep(100);
          }
     }
}

(Hab ein bisschen abgekürzt)

Ist das jetzt zu kompliziert?
Statt dem Thread.Sleep(100) könnte man jetzt nen Timer benutzen, aber macht das irgendeinen großen Unterschied?

Danke für die Antworten!

Gruß,
Mossos

U
1.688 Beiträge seit 2007
vor 11 Jahren

Hallo,

und das funktioniert? Du wartest in "SendMessage" auf ein Ereignis, dass Du erst in ApplyResult setzt?
Und welche Queue-Implementierung benutzt Du? Hoffentlich eine thread-sichere.

M
Mossos Themenstarter:in
47 Beiträge seit 2011
vor 11 Jahren

Bei der Queue hab ich mich an die SyncQueue < T > von herbivore gehalten.

Wieso sollte es nicht funktionieren? Ich habe für SendMessage bis jetzt nur ne Dummy-Methode benutzt, aber damit klappt es. Woran könnte es denn scheitern? Ich werde in der Methode natürlich darauf achten, dass sie irgendwann abbricht, falls sie vom EndServer keine Antwort bekommt.

EDIT: Ach jetzt hab ich erst verstanden was du gemeint hast. Ne also SendMessage ist eine Methode die den Message-String von dem Message-Objekt an einen Server schickt und dessen Antwort-String zurückgibt. Diese Methode wartet nicht auf ein Event. Oh ja die Namensgebung ist etwas irreführend ^^ Sorry, ich verbesser das.

1.346 Beiträge seit 2008
vor 11 Jahren

Einen Thread würde ich so direkt nicht benutzen. Da bekommst du unter umständen den Mindestabstand von 2 Sekunden nicht mit. Du musst ja, wenn du etwas gesendet hast zwei Sekunden warten. Der Timer würde ja den Start des Sendevorgangs als Anhaltspunkt benutzen. Demnach ist die Sleep Methode gar nicht so verkehrt. Dennoch würde ich das leicht anders lösen und die Task.Delay Methode verwenden. Die benutzt intern einen Timer, blockiert den Thread also nicht.

Dann würde ich als Queue entweder herbivores nutzen, aber auch die neue ConcurrentQueue<T> aus dem Framework bietet sich an.

Das locking um auf das Ergebnis zu warten finde ich auch eher suboptimal.

So, oder so ähnlich würde ich das lösen:

   class Program
    {
        static void Main(string[] args)
        {
            Message m1 = new Message();
            Message m2 = new Message();
            Message m3 = new Message();

            Sender sender = new Sender();

            var result1 = sender.SendMessageAsync(m1);
            var result2 = sender.SendMessageAsync(m2);
            var result3 = sender.SendMessageAsync(m3);

            result1.ContinueWith(a => Console.WriteLine("Result 1"));
            result2.ContinueWith(a => Console.WriteLine("Result 2"));
            result3.ContinueWith(a => Console.WriteLine("Result 3"));

            sender.Start();

            Console.ReadKey();
        }
    }

    public class Sender
    {
        CancellationTokenSource _tokenSource;

        ConcurrentQueue<Tuple<TaskCompletionSource<Result>, Message>> _messages = new ConcurrentQueue<Tuple<TaskCompletionSource<Result>, Message>>();

        public Sender()
        {
        }

        public Task<Result> SendMessageAsync(Message message)
        {
            if (message == null) throw new ArgumentNullException("message");
            var tcs = new TaskCompletionSource<Result>();

            _messages.Enqueue(new Tuple<TaskCompletionSource<Result>, Message>(tcs, message));

            return tcs.Task;
        }

        public async Task SendingThread()
        {
            while (_tokenSource != null && !_tokenSource.IsCancellationRequested)
            {
                Tuple<TaskCompletionSource<Result>, Message> message;

                if (_messages.TryDequeue(out message))
                {
                    try
                    {
                        var result = await SendMessageAsync(message.Item2.RequestMessage);

                        message.Item1.SetResult(new Result { ResultingString = result });

                        await Task.Delay(2000);
                    }
                    catch (Exception ex)
                    {
                        message.Item1.SetException(ex);
                    }
                }
                else
                {
                    await Task.Delay(100);
                }
            }

            foreach (var item in _messages)
            {
                item.Item1.SetCanceled();
            }
        }

        private async Task<string> SendMessageAsync(string message)
        {
            //TODO: send message here

            return message;
        }

        public void Stop()
        {
            if (_tokenSource != null && !_tokenSource.IsCancellationRequested)
                _tokenSource.Cancel();
        }

        public async void Start()
        {
            if (_tokenSource == null || _tokenSource.IsCancellationRequested)
                _tokenSource = new CancellationTokenSource();
            await SendingThread();
        }
    }

    public class Message
    {
        public string RequestMessage { get; set; }
    }

    public class Result
    {
        public string ResultingString { get; set; }
    }
6.911 Beiträge seit 2009
vor 11 Jahren

Hallo pdelvo,

deine Lösung bringt aber Mossos nichts, da er .net 3.5 verwenden muss.
Auch könnte deine Lösung eleganter umgesetzt werden, wenn eine eigene BlockingCollection<T> verwendet wird, die beim Enumerator von GetComsonsumingEnumerable intern den Mindestabstand der 2 Sek. berücksichtigt. Alternativ könnte auch Add solange blockieren, aber das finde ich in Hinblick auf Skalierbarkeit des Webservices nicht so toll.

Hallo Mossos,

was macht dann der Webservice mit der Antwort? Liefert er diese einfach an den Client zurück?
Meine Nachfragen deshalb, da ich das "Konstrukt" etwas umständlich finde und auch schlecht Skalierbar, wenn der Webservice-Request-Thread blockiert bis das Ergebnis da ist. Es gibt sicherlich elegantere Möglichkeiten (z.B. Callbacks wenn das Ergebnis vom anderen Server da ist), aber dazu benötige ich ein besseres Verständnis was wirklich pasieren soll.

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

M
Mossos Themenstarter:in
47 Beiträge seit 2011
vor 11 Jahren

@pdelvo
Muss leider .Net 3.5 benutzen, aber trotzdem danke.

@gfoidl
Ich benutzte die WebServiceHost-Klasse für meinen WebService. Die Antwort vom Server wird einfach an den Client zurück geliefert.