Laden...

WCF: Asynchrone Webservice-Methoden erzeugen

Erstellt von TiloS vor 10 Jahren Letzter Beitrag vor 10 Jahren 4.330 Views
T
TiloS Themenstarter:in
35 Beiträge seit 2012
vor 10 Jahren
WCF: Asynchrone Webservice-Methoden erzeugen

Hallo,

ich möchte aus einer synchronen Webservice-Methode (WCF-Webservice) eine asynchrone machen.
Nachdem was ich gelesen habe, sollte es so gehen:

  • in der ServiceReference "Asynchrone Vorgänge generieren" anklicken
  • Die Webservice-Methode muss das Attribut [WebMethod] besitzen
    Dann sollte man im Client zusätzliche, automatisch generierte Methoden vorfinden, die mit "Begin..." beginnen.

Dort liegen sie aber in der Webservice-Klasse nicht vor. Fehlt da noch was, habe ich einen Denkfehler, oder wo könnte das Problem liegen?

Die WebService-Methode sieht so aus:


public class MyWebService : IMyWebService
{
   [WebMethod]
    public string Order(string param)
    {
        Thread.Sleep(10000);
        return "";
    }
}


    [ServiceContract(
        Namespace = "NS",
        SessionMode = SessionMode.Allowed)]
    public interface IMyWebService
    {
        [OperationContract]
        [WebInvoke(Method = "POST",
            RequestFormat = WebMessageFormat.Xml,
            ResponseFormat = WebMessageFormat.Xml)]
        string Order(string param);
    }

Bin für jede Hilfe dankbar.

Gruß
TiloS

6.911 Beiträge seit 2009
vor 10 Jahren

Hallo TiloS,

da hast du alles vermischt bzw. durcheinander gebracht: WCF, Asp.net WebSerivce (asmx) und Rest, sowie Server- und Client-Code.

Das WebMethod-Attribut gehört zu den asmx-Webservice und werden bei WCF nicht benötigt. Bei WCF reicht die Deklaration einer Schnittstelle (mit Klassen gehts auch, empfehle ich aber nicht) mit dem ServiceContract-Attribut.

Für asynchrone Methoden bzw. Vorgänge muss Server und Client getrennt betrachtet werden. D.h. es gibt insgesamt 4 Möglichkeiten:


Server  synchron + Client  synchron
Server  synchron + Client asynchron
Server asynchron + Client  synchron
Server asynchron + Client asynchron

Ab .net 4.5 hat sich das in WCF gewaltig vereinfacht, indem im ServiceContract eine Methode mit Task<TResult> deklariert werden kann. WCF geniert dann automatisch ein Methoden-Paar - eines für die synchrone Verarbeitung und eines für die asynchrone.
Clientseitig können per "Add Servicereference" ebenfalls Task-Methoden sowie die synchronen Methoden generiert werden. Das ist jetzt sogar die Standardeinstellung. Somit kann beim Client mit async/await bequem der Service konsumiert werden.

Vor .net 4.5 kann clientseitig die asnychrone Methode vorzugsweise nach dem Event-based asynchronous pattern konsumiert werden. D.h. es wird eine XXXAsync-Methode beim Client aufgerufen und sobald diese am Server (ob dieser synchron od. asynchron arbeitet spielt für den Client keine Rolle) verarbeitet wurde wird ein XXXCompleted-Eregnis gefeuert. Alternativ zum ereignis-basierten Vorgehen kann auch mit den BeginXXX-/EndXXX-Methoden gearbeitet werden, aber das ist mMn unnötig kompliziert.

Auf der Server-Seite werden asynchrone Methode wie folgt definiert:


[ServiceContract]
public interface IMyService
{
    [OperationContract(AsyncPattern=true)]
    IAsyncResult BeginMyMethod(string input, AsyncCallback callback, object state);
    string EndMyMethod(IAsyncResult ar);
}

Es gibt also ein Methoden-Paar das entsprechend der Konvention von/für WCF BeginXXX und EndXXX heißt. Innerhalb der BeginXXX-Methode soll dann der asynchrone Vorgang entsprechend APM angestossen werden. Wenn du aber die Chance hast .net 4.5 zu nutzen, so reicht


public interface IMyService
{
    [OperationContract]
    Task<string> MyMethod(string input);
}

Für weitere Infos siehe Synchronous and Asynchronous Operations.

BTW: Thread.Sleep solltest du tunlichst niemals in Service-Methoden verwenden, das ist komplett gegen den Sinn von asynchronen Service-Methoden.

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

T
TiloS Themenstarter:in
35 Beiträge seit 2012
vor 10 Jahren

Hallo Gü,

danke für Deine Antwort.
Es ist doch nicht so einfach, wie ich am Anfang dachte. Ich sehe schon etwas klarer, bin aber noch nicht am Ziel.

Ich möchte das in .NET 4.0 umsetzen. Aus folgenden Gründen ist es wahrscheinlich besser, das Server-seitig asynchron zu machen.

  1. Ich möchte dem Kunden der das auch nutzen soll clientseitigen Aufwand ersparen.
  2. Ich möchte im Webservice eine ID für den Vorgang generiern, und diese schon dem Client zukommen lassen, noch bevor der komplette Vorgang abgearbeitet ist, da das recht lange dauern kann. Der Client soll dann anhand dieser ID Statusprüfungen machen und sich dann das Ergebnis abholen.

Mittlereile bin ich soweit:


[ServiceContract]
public interface IMyService
{
    [OperationContract(AsyncPattern=true)]
    IAsyncResult BeginMyMethod(string input, AsyncCallback callback, object state);
    string EndMyMethod(IAsyncResult ar);
}

public class MyService : IMyService
{
    public IAsyncResult BeginMyMethod(string input, AsyncCallback callback, object asyncState)
    {
        MyAsyncResult asyncResult = null;
        asyncResult = new MyAsyncResult(input, callback, asyncState);
        
        return asyncResult;
    }

    public string EndMyMethod(IAsyncResult ar)
    {
        MyAsyncResult asyncResult = ar as MyAsyncResult;
        return asyncResult.result;
    }
}

    public class AsyncResult : IAsyncResult, IDisposable
    {
        AsyncCallback callback;
        object state;
        ManualResetEvent manualResentEvent;

        public AsyncResult(AsyncCallback callback, object state)
        {
            this.callback = callback;
            this.state = state;
            this.manualResentEvent = new ManualResetEvent(false);
        }
    }

    public class MyAsyncResult : AsyncResult
    {
        private int ID { get; set; }
        private string result { get; set; }
        public string input { get; set; }
        public Exception Exception { get; set; }

        public int Result
        {
            get { return result; }
            set { result = value; }
        }

        public MyAsyncResult(string input, AsyncCallback callback, object state)
            : base(callback, state)
        {
            this.input = input;
        }
    }

Mir ist nur noch nicht ganz klar wo (sicher in der BeginMyMethod) bzw. wie ich dort meine eigentliche Methode "MyMethod" aufrufe.

Die ID des Vorgangs müsste ich dann in der BeginMyMethod vor allem anderen erzeugen und dem asyncResult mitgeben.

Im Client sollte das dann nach meinem Stand so aussehen:


IAsyncResult res = service.BeginMyMethod(input, new AsyncCallback(MyCallback), service); 

static void MyCallback(IAsyncResult ar) 
{ 
    IMyService res = ar.AsyncState as IMyService; 
    Label1.Text = res.EndMyMethod(ar).Result.ToString()); 

Bin ich soweit auf dem richtigen Weg? Bzw. wie baue ich da die MyMethod ein?

Gruß
Tilo

6.911 Beiträge seit 2009
vor 10 Jahren

Hallo TiloS ,

das Server-seitig asynchron zu machen.

  1. Ich möchte dem Kunden der das auch nutzen soll clientseitigen Aufwand ersparen.

Das hat damit nichts zu tun. Der Client ist vom Server "entkoppelt". Für dein Verständnis beschreibe ich grob wie bei WCF die Kommunikation erfolgt.

Für SOAP wird aus dem ServiceContract die Beschreibung (WSDL) abgeleitet und anhand dieser können Server und Client kommunizieren.1.Client ruft eine Methode auf. 1.Die WCF schaut welche Action zur Methode passt und ruft die Action auf. 1.Der Server erhält einen Aufruf, anhand der Action weiß die WCF welche Methode welches Objekts aufgerufen werden muss. 1.Die Service-Methode (lt. OperationContract) arbeitet die Methode ab. 1.Die Service-Methode schreibt das Ergebnis zurück zu WCF. 1.WCF sendet die Antwort über die ReplyAction zurück zum Client. 1.Beim Client nimmt WCF die Antwort aus der ReplyAction an und gibt sie an deinen Client weiter.

Schau dir dazu auch die WSDL (http://localhost:8893/myService.svc?wsdl) an, damit du besser verstehst worauf sich das bezieht. Mach das aber besser bei einem einfachen Service, denn sonst wird es gleich unübersichtlich.

Zurück zu den Schritten:
Der Client hat nach dem Methoden-Aufruf 2 Möglichkeiten:*Warten bis die Antwort kommt -> synchron *Nicht Warten -> asynchron. Dann ruft die WCF nach erhalt der ReplyAction die EndXXX-Methode auf od. feuert ein entsprechendes Ereignis od. vervollständigt den Task - je nach dem wie der Client erstellt wurde.

Dem Client ist somit ganz egal ob der Server synchron od. asynchron arbeitet. Dem Client ist nur wichtig, dass nach seiner Action etwas passiert und dass er (irgendwann) eine ReplyAction erhält.
Ist der Client asynchron mit den Begin/End-Methoden so muss der Begin-Methode ein AsyncCallback und ein State-Objekt mitgegeben werden. Das AsyncCallback benötigt WCF um nach der ReplyAction die korrekte Methode aufzurufen und das State-Objekt wird verwendet um bei vielen gleichzeitigen Aufrufen von WCF den richtigen Aufruf zu erkennen (über die Objekt-Identität).

Dem Server ist es ebenso egal ob der Client synchron od. asynchron arbeitet. Der Server bekommt eine Anfrage per Action und wenn er fertig ist schreibt er mittels ReplyAction die Antwort zum Client. Der Server kann dies synchron od. asynchron erledigen.

Du siehst also, dass Server und Client in Bezug auf synchron/asynchron komplett unabhängig sein können. D.h. auch, wenn z.B. die Service-Methode extrem lange dauert und der Server asynchron arbeitet, der Client aber synchron, so blockiert der Client eben solange bis das Ergebnis vorhanden ist.

Den Server asynchron zu machen bringt eigentlich nur dann etwas, wenn dieser selbst längere Operationen durchführt, die ihrerseits asynchron erfolgen können wie z.B. den Aufruf eines anderen Services, dem Dateizugriff, etc. da der WCF-Thread, der die Anfrage übernommen hat, sofort wieder zurück in den Thread-Pool kann und ein I/O-Abschlussthread nur die ReplyAction schreiben braucht. Es stehen somit mehr CPU-Threads zur Verfügung und der WCF-Service skaliert besser.
Für lange Berechnungen ist nicht viel gewonnen, da so od. so ein CPU-Thread auf dem Server beschäftigt ist und somit ist es egal ob der WCF-Thread, der die Anfrage übernommen hat, auch die Berechnung ausführt od. nicht.

  1. Ich möchte im Webservice eine ID für den Vorgang generiern, und diese schon dem Client zukommen lassen, noch bevor der komplette Vorgang abgearbeitet ist, da das recht lange dauern kann. Der Client soll dann anhand dieser ID Statusprüfungen machen und sich dann das Ergebnis abholen.

Auch das hat nichs mit synchron/asynchron zu tun.

Um das Problem zu lösen fallen mir 2 Möglichkeiten ein (in Klammern hab angemerkt ob synchron od. asnychron - nur so als Richtlinie):*Verwendung von Sessions 1.Client startet Session 1.Client holt sich (synchron) die ID vom Server ab. 1.Client startet asynchron den langen Vorgang am Server. 1.Sobald der Server fertig ist teilt er dies dem Client mit (entweder per End-Methode, Ereignis od. Task) 1.Client holt sich (synchron) das Ergebnis vom Server. 1.Client beendet die Session.

Das Ergebnis könnte der Server auch dem Client direkt in Schritt 4 mitteilen, Schritt 5 entfällt somit (und Schritt 6 wird zu Schritt 5 😉).
*Verwendung von Callbacks
Hinweis: Callbacks funktionieren aber nicht mit BasicHttpBindung, da HTTP zustandlos ist und für den Callback der Server eine Verbindung zum Client benötigt.

1.Client ruft (asynchron) die Server-Methode auf 1.Server erstellt die ID und teilt diese dem Client via Callback mit und setzt seine Arbeit fort. 1.Sobald der Server fertig ist, sendet er das Ergebnis an den Client.

Der Zwischenschritt der Rückgabe der generierten ID hat also nichts mit synchron/asynchron zu tun.

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

T
TiloS Themenstarter:in
35 Beiträge seit 2012
vor 10 Jahren

Hallo Gü,

ganz großen Dank für Deine ausführlichen Erklärungen.

Ich hätte es also auch genauso gut mit asynchronem Client-seitigen Aufruf lösen können.
Ich habe aber jetzt Beginxxx- und Endxxx-Methoden, sowie einem eigenen AsyncResult auf dem Server eingerichtet.
Und meine Lösung funktioniert erstmal. Ich habe es an Deine Möglichkeit 1 angelehnt:

  1. Client startet die Beginxxx-Methode, die sofort die OrderID zurückgibt.
  2. Der Server fährt dann fort und startet asynchron den langen Vorgang.

Bis hierhin war das mein Hauptanliegen, und das funktioniert auch. Dann gibt es mehrere Möglichkeiten sich das Ergebnis abzuholen.
3. Der Einfachheit halber prüfe ich manuell mit einen "Prüfen"-Button den Status meines States per synchronem Webservice-Aufruf. Der State wird anhand der OrderID identifiziert.
4. Client holt sich (synchron) das Ergebnis vom Server. Per End-Methode wäre eleganter, da bekommt der Client auch das Ergebnis, nur mit der Anzeige dieses hapert es noch, da an der Stelle die Webseite nicht automatisch aktualisiert wird (aber vielleicht bekomme ich das noch hin).

Hier der Code dazu:


public IAsyncResult BeginOrder(string input)
{
    OrderAsyncResult asyncResult = new OrderAsyncResult(GetOrderID(), input, null, null);
    ThreadPool.QueueUserWorkItem(new WaitCallback(CallbackOrder), asyncResult);
    return asyncResult;
}

private void CallbackOrder(object state)
{
    OrderAsyncResult asyncResult = state as OrderAsyncResult;
    asyncResult.Result = InternalOrder(asyncResult.OrderID, asyncResult.Input);
    asyncResult.Complete();
}

public string EndOrder(IAsyncResult ar)
{
    string result = "";
    using (OrderAsyncResult asyncResult = ar as OrderAsyncResult)
    {
        asyncResult.AsyncWait.WaitOne();
        result = asyncResult.Result;
    }
    return result;
}

Das OrderAsyncResult ist ein von IAsyncResult abgeleitetes Objekt mit zusätzlichen Eigenschaften für OrderID, Result und Input.
Auf diese Weise habe ich dann im AsyncResult die ID:


OrderAsyncResult res = srv.BeginOrder(input) as OrderAsyncResult;
string orderID = res.OrderID;

Die synchronen Webservice-Aufrufe für das Prüfen und das Holen des Ergebnisses sind dann recht unspektakulär.


bool statusReady = srv.CheckOrder(orderID);

und


string result = srv.GetResult(orderID);

Dein Link für die WSDL funktioniert leider nicht:

Schau dir dazu auch die WSDL (
>
) an, damit du besser verstehst worauf sich das bezieht. Mach das aber besser bei einem einfachen Service, denn sonst wird es gleich unübersichtlich.

Könntest Du da nochmal einen Link geben. Das Thema WSDL wäre für mich noch sehr interessant.

In meiner WSDL, die von dem erstellten Webservice gezogen wird, erscheinen nämlich nicht die Beginxxx und Endxxx-Methoden. Vom Client, der die Projekt-dll referenziert, habe ich natürlich Zugriff, aber ich bin mir nicht sicher, ob der Aufruf von einem entfernten Client genauso funktionieren würde.

Eigentlich müssten doch die BeginOrder und EndOrder-Methoden in der WSDL sichtbar sein? Oder sieht das bei asynchronen Methoden anders aus?

Gruß
Tilo

6.911 Beiträge seit 2009
vor 10 Jahren

Hallo TiloS,

Ich hätte es also auch genauso gut mit asynchronem Client-seitigen Aufruf lösen können.

Ja.

Ich habe aber jetzt Beginxxx- und Endxxx-Methoden, sowie einem eigenen AsyncResult auf dem Server eingerichtet.

Wie vorhin erwähnt bringt das nicht immer Vorteile.

Den Server asynchron zu machen bringt eigentlich nur dann etwas, wenn dieser selbst längere Operationen durchführt, die ihrerseits asynchron erfolgen können wie z.B. den Aufruf eines anderen Services, dem Dateizugriff, etc. da der WCF-Thread, der die Anfrage übernommen hat, sofort wieder zurück in den Thread-Pool kann und ein I/O-Abschlussthread nur die ReplyAction schreiben braucht. Es stehen somit mehr CPU-Threads zur Verfügung und der WCF-Service skaliert besser.
Für lange Berechnungen ist nicht viel gewonnen, da so od. so ein CPU-Thread auf dem Server beschäftigt ist und somit ist es egal ob der WCF-Thread, der die Anfrage übernommen hat, auch die Berechnung ausführt od. nicht.

D.h. u.U. hast du nur mehr Aufwand für die Implementierung und diese muss auch korrekt erfolgen.
Was macht bei dir die GetOrderID-Methode? Wenn diese z.B. auf eine Datenbank zugreift, so sollte dieser Zugriff auch asynchron erfolgen, denn sonst blockiert halt ein anderer (CPU-) Thread solange bis das Ergebnis vorhanden ist und in Bezug auf Skalierbarkeit - den eigentlichen Grund warum man die Service-Methode asynchron macht - nichts gewonnen, sondern eher sogar verschlechtert worden.

Mit den Begin-/End-Methoden ist das nicht immer so trivial und einfach wie es scheinen mag. So wie ich deinen Code sehe wird dort noch ein großer Teil synchron verarbeitet, da das asynchrone Pattern nicht korrekt implementiert ist.

Da du eh .net 4.0 verwenden willst, kannst du Tasks zur Hilfe nehmen, denn diese implementieren die IAsyncResult-Schnittstelle und somit ist die Implementierung schon wesentlich einfacher.
Lass mich das anhand eines sehr einfachen Beispiels zeigen. Es geht dabei um die Addition von 2 Zahlen:


[ServiceContract]
public interface ICalculator
{
	[OperationContract(AsyncPattern = true)]
	IAsyncResult BeginAdd(int a, int b, AsyncCallback callback, object state);
	int EndAdd(IAsyncResult ar);
}

public class Calculator:ICalculator
{
	public IAsyncResult BeginAdd(int a, int b, AsyncCallback callback, object state)
	{
		Task<int> task = Task.Factory.StartNew(_ =>
		{
			return a + b;
		}, state);

		if (callback != null)
			task.ContinueWith(t => callback(t), TaskContinuationOptions.ExecuteSynchronously);

		return task;
	}
	//---------------------------------------------------------------------
	public int EndAdd(IAsyncResult ar)
	{
		return (ar as Task<int>).Result;
	}
}

In der BeginAdd-Methode wird ein Task<int> gestartet und dieser Task<int> wird zurückgegeben. Sobald dieser Task fertig ist wird das AsyncCallback aufgerufen (da er ContinueWith angehängt) welches der WCF-Infrastruktur mitteilt dass die asynchrone Verarbeitung fertig ist und die WCF ruft dann die EndAdd-Methode auf welche ihrerseits dann die ReplyAction schreibt und der Client erhält das Ergebnis. Zugegeben für diese einfache Aufgabe ist das Overkill, denn der Aufwand ist höher als der Nutzen, aber es für das Verständnis ist es (immer) besser einfache Beispiele zu verwenden.

In der Begin-Methode geht es also darum möglichst schnell einen asynchronen Vorgang zu starten und ein IAsyncResult zurückzugeben - sonst ist nichts gewonnen, v.a. dann nicht wenn die Begin-Methode sequentiell arbeitet.
Daher verstehe ich nicht wie du dir

  1. Client startet die Beginxxx-Methode, die sofort die OrderID zurückgibt.

vorgestellt hast. Das kann so nicht funktionieren, zumindest nicht mit dem Hintergrund dass die Begin-Methe das im vorigen Satz erwähnte durchführen soll.

Vorhin bei dem Punkt "Verwendung von Sessions" sind für die Punkte 1-3 auch 3 Methoden notwendig (bzw. 2 je nachdem wie die Session konfiguriert ist).

Schau dir die Themen nochmal an, damit du das wirklich verstehst.

da an der Stelle die Webseite nicht automatisch aktualisiert wird

An diesem Punkt kann ich dir nicht weiterhelfen, aber das wäre ohnehin ein neues Thema (Punkt 1.2) das du gerne in Web-Technologien behandeln kannst. Irgendwas mit WCF/Ajax gibt es dazu sicher 😉.

Dein Link für die WSDL funktioniert leider nicht:

Klar, denn das ist ein Link für meinen Entwickluns-Service (localhost) und sollte nur verdeutlichen, dass du dir die WSDL für deine Service durch anhängen von ?wsdl an die Service-Adresse erzeugen lassen kannst.

Eigentlich müssten doch die BeginOrder und EndOrder-Methoden in der WSDL sichtbar sein? Oder sieht das bei asynchronen Methoden anders aus?

Diese müsse nicht sichtbar sein, denn - wie bereits öfters erwähnt - ist es dem Client egal ob der Server asnychron od. synchron arbeitet und umgekehrt. Daher macht es keinen Sinn in der Beschreibung, der WSDL, dies festzuhalten.

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

T
TiloS Themenstarter:in
35 Beiträge seit 2012
vor 10 Jahren

Was macht bei dir die GetOrderID-Methode?

Die erzeugt nur einen TimeStamp.

private string GetOrderID()
{
    return DateTime.Now.ToString("yyyyMMddHHmmssffff");
}

Die ist also direkt da und ich übergebe sie dem AsyncResult. Das AsyncResult wird dann mit der OrderID zurückgegeben. Jetzt habe ich sie schonmal im Client lange befor ich das eigentliche Ergebnis bekomme. Das hab ich auch so schon am Laufen.
Die OrderID wird dann weiterhin im Server dazu verwendet, den langen Prozess zustarten. Ich gebe sie also dem State mit.
Auf diesen State kann ich dann synchron mit der OrderID zugreifen.
So habe ich momentan im Client den Vorteil, dass ich weiterarbeiten kann, während im Hintergrund die Berechnung läuft.

Mein Problem ist momentan, dass das Ganze nur intern läuft, also aspx-Seite und Webservice auf dem selben Server mit Zugriff auf die entsprechenden DLL's. Von Extern hat der Client keinen Zugriff auf OderAsyncResult. Könnte man das lösen, indem man das OrderAsyncResult nochmal im Client anlegt. Es wiederstebt mir zwar Sachen doppelt zu machen, aber wenn es helfen würde?

Diese müsse nicht sichtbar sein, denn - wie bereits öfters erwähnt - ist es dem Client egal ob der Server asnychron od. synchron arbeitet und umgekehrt. Daher macht es keinen Sinn in der Beschreibung, der WSDL, dies festzuhalten.

Wenn ich es theoretisch serverseitig lösen wöllte, müssten doch auch entsprechende Methoden sichtbar sein, die ich dann aufrufen will. Ich möchte ja z.B. von einem externen Client die BeginOrder aufrufen. Müsste sie in dem Fall nicht sichtbar sein?

  1. Variante:
    Jetzt bin ich aber doch schon fast soweit die Webservice-Aufrufe clientseitig asynchron aufzurufen. Dann würde ich Deine 1. Möglichkeit (Verwendung von Sessions) implementieren.

Die Session würde ich doch nur dazu nutzen, die Aufrufe für einen Vorgang zu identifizieren. Würde das nicht auch mit der ID gehen? 1. Client holt sich (synchron) die ID vom Server ab.
2. Client startet asynchron den langen Vorgang am Server (mit Übergabe der ID).
3. Sobald der Server fertig ist teilt er dies dem Client mit (entweder per End-Methode, Ereignis od. Task) (mit Übergabe der ID)
4. Client holt sich (synchron) das Ergebnis vom Server (mit Übergabe der ID).

Gruß
Tilo

6.911 Beiträge seit 2009
vor 10 Jahren

Hallo TiloS,

Die ist also direkt da und ich übergebe sie dem AsyncResult. Das AsyncResult wird dann mit der OrderID zurückgegeben

Sorry, mein Fehler. Hab übersehen dass das AsyncResult diese Eigenschaft hat.

Könnte man das lösen, indem man das OrderAsyncResult nochmal im Client anlegt

Wäre eine Möglichkeit und Tools wie svcutil.exe (in VS bei Add Service-Reference wird das verwendet) machen das auch, aber mir widerstrebt das wegen DRY auch.
Eine elegantere Lösung wäre den ServiceContract in ein eigenes Projekt auszulagern (samt allem dazu benötigten wie das OrderAsyncResult) und dieses vom Server und Client zu referenzieren.
Da ich den Client-Code auch selbst schreibe weiß ich jetzt nicht wie svcutil dann damit umgeht bzw. ob es korrekt erkennt dass dieser Typ bereits vorhanden ist. Aber den Client-Code zu erstellen ist recht trivial - schau dir einfach mal an nach welchem Prinzip die generierten Klassen aufgebaut sind, wichtig ist dabei v.a. ClientBase<T>. Du kannst dich auch an WCF: Basisklasse für einen Proxy orientieren.

Ich möchte ja z.B. von einem externen Client die BeginOrder aufrufen.

Der Client will die Order-Methode aufrufen und ob der Server die asynchron od. synchron macht ist dem Client wurscht. Der Client selbst kann asynchron sein und der Server synchron (siehe 1. Antwort). Daher wäre es nicht zielführend das in der WSDL zu manifestieren, man würde sich die Möglichkeiten verbauen und daher ist es besser das nicht explizit anzugeben.

Würde das nicht auch mit der ID gehen?

Würde gehen. So wie du die Punkte angeführt hast müsste das Ergebnis aber zwischengespeichert werden und auf das kann verzichtet werden, indem bei Punkt 3 gleich das Ergebnis an den Client geschickt wird. Punkt 4. entfällt dann. Ganz gefällt mir diese Lösung aber auch noch nicht, da der Client 2x einen Service-Aufruf starten muss. Bei deiner vorigen Lösung reicht 1 Aufruf und daher ist das eher die bessere Lösung.

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

T
TiloS Themenstarter:in
35 Beiträge seit 2012
vor 10 Jahren

Hallo Gü,

Eine elegantere Lösung wäre den ServiceContract in ein eigenes Projekt auszulagern (samt allem dazu benötigten wie das OrderAsyncResult) und dieses vom Server und Client zu referenzieren.

Das ist eine super Idee. Damit müsste es gehen.

Der Client will die Order-Methode aufrufen und ob der Server die asynchron od. synchron macht ist dem Client wurscht. Der Client selbst kann asynchron sein und der Server synchron (siehe 1. Antwort). Daher wäre es nicht zielführend das in der WSDL zu manifestieren, man würde sich die Möglichkeiten verbauen und daher ist es besser das nicht explizit anzugeben.

Das ist jetzt wieder die Client-asynchron-Methode?

Bei deiner vorigen Lösung reicht 1 Aufruf und daher ist das eher die bessere Lösung.

Bei der Server-asynchron-Methode stehe ich noch auf dem Schlauch. Muss ich dabei die Order oder die Begin-Order-Methode aufrufen?
Die Order-Methode bringt mir doch nicht viel, die ist ja auf dem Server nicht asynchron und die liefert auch kein AsynResult.


MyServiceClient srv = new MyServiceClient();
//OrderAsyncResult res = srv.BeginOrder(input, null, null) as OrderAsyncResult;
//geht nicht, da BeginOrder nicht bekannt
//bzw.
string res = srv.Order(input);

MyServiceClient ist dabei eine bei "Add Service Reference" automatisch erzeugte Klasse.

Oder gibt es irgendwo ein Beispiel, wie ich am Client durch Aufruf der Order-Methode die BeginOrder auf dem Server anstoße und auch das AsyncResult zurückbekomme?

Da ich den Client-Code auch selbst schreibe weiß ich jetzt nicht wie svcutil dann damit umgeht bzw. ob es korrekt erkennt dass dieser Typ bereits vorhanden ist. Aber den Client-Code zu erstellen ist recht trivial - schau dir einfach mal an nach welchem Prinzip die generierten Klassen aufgebaut sind, wichtig ist dabei v.a. ClientBase<T>. Du kannst dich auch an
>
orientieren.

In deinem Beispiel WCF: Basisklasse für einen Proxy für den Code im Client scheint auch noch was in "Contracts" bzw. "gfoidl.Service.Utils" drin zu sein, was nicht ersichtlich ist (ProxyBase, Bindings, endpointAddress).

Gruß
Tilo

6.911 Beiträge seit 2009
vor 10 Jahren

Hallo TiloS,

Die Order-Methode bringt mir doch nicht viel, die ist ja auf dem Server nicht asynchron und die liefert auch kein AsynResult.

Nimm an dass auf dem Server die BeginOrder- und EndOrder-Methoden vorhanden sind, aber keine Order-Methode. Weiters nimm an dass auf dem Client nur die Order-Methode vorhanden ist.
Wenn nun der Client die (synchrone) Order-Methode aufruft, so verarbeitet der Server die Order asynchron. Das ist möglich, da in der WSDL nur die Action und ReplyAction festgelegt ist, aber nicht ob es synchron od. asynchron stattzufinden hat.

Das ist jetzt wieder die Client-asynchron-Methode?

Nicht unbedingt, denn Client und Server sind unabhängig. Lies dir die Posts dieses Threads nochmals durch - ich weiß es ist schwierig zu verstehen, aber sobald du das Aha-Erlebnis hast wird es einfach.

wie ich am Client durch Aufruf der Order-Methode die BeginOrder auf dem Server anstoße und auch das AsyncResult zurückbekomme?

Wenn du am Client auf das IAsyncResult angewiesen bist, so muss dieser asynchron arbeiten.

...was nicht ersichtlich ist (ProxyBase, Bindings, endpointAddress).

Lad dir den Anhang herunter, dort ist das zu sehen.

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

T
TiloS Themenstarter:in
35 Beiträge seit 2012
vor 10 Jahren

Hallo Gü,

Nimm an dass auf dem Server die BeginOrder- und EndOrder-Methoden vorhanden sind, aber keine Order-Methode.

Das war das erste Problem, ich hatte nämlich noch eine Order-Methode auf dem Server, zu Testzwecken das Ganze komplett synchron. Ok, verstanden.

Weiters nimm an dass auf dem Client nur die Order-Methode vorhanden ist.
Wenn nun der Client die (synchrone) Order-Methode aufruft, so verarbeitet der Server die Order asynchron.

Das Prinzip habe ich jetzt auch verstanden. Damit würde es gehen.

Nicht unbedingt, denn Client und Server sind unabhängig. Lies dir die Posts dieses Threads nochmals durch - ich weiß es ist schwierig zu verstehen, aber sobald du das Aha-Erlebnis hast wird es einfach.

Der dürfte jetzt eingetreten sein 😃

Wenn du am Client auf das IAsyncResult angewiesen bist, so muss dieser asynchron arbeiten.
...was nicht ersichtlich ist (ProxyBase, Bindings, endpointAddress).
Lad dir den Anhang herunter, dort ist das zu sehen.

Ok, man muss halt ein bischen weiter lesen. Jetzt ergibt sich ein komplettes Bild.

Das einzige, was mich noch wundert, ist, dass da am Client noch so viel Code zu schreiben ist.

Und da ist mir noch ein Geistesblitz gekommen. Ich habe es ohne den vielen Client-Code jetzt an einer Stelle anders gelöst, was aber genau meine Anforderung erfüllt:1. Client holt sich (synchron) die ID vom Server ab.
2. Server startet (asynchron) den langen Vorgang am Server. (das ist der Witz)
3. Client prüft (synchron) ob das Ergebnis fertig ist.
4. Client holt sich (synchron) das Ergebnis vom Server. Dazu habe ich am Server einfach eine neue synchrone Methode StartOrder, die die BeginOrder asynchron startet und gleichzeitig die gewünschte ID zurückgibt.


public string StartOrder(string input)
{
    OrderAsyncResult res = BeginOrder(input, null, null) as OrderAsyncResult;
    return res.orderID;
}

Damit komme ich zwar nicht automatisch das Ergebnis geleifert, aber die Berechnungen laufen im Hintergrund asynchron. Und die zusätzliche synchrone Abfrage ist für meine Zwecke ausreichend.
Und wenn ich es doch noch machen muss, weiß ich jetzt wie es geht.

Danke und Gruß
Tilo

T
TiloS Themenstarter:in
35 Beiträge seit 2012
vor 10 Jahren

Hallo nochmal,

nachdem alles so wunderbar funktioniert hat, gibt es jetzt noch ein Problem. Innerhalb meines Webservices (WS1) möchte ich einen weiteren Webservice (WS2) meines Kollegen für spezielle Berechnungen aufrufen. Der Webserviceaufruf soll erstmal nur intern erfolgen. Also ich habe die entsprechende DLL eingebunden und (Korrektur:) einen direkten Zugriff realisiert.

Rufe ich meinen WS1 synchron auf, kann ich innerhalb dieses den WS2 problemlos aufrufen, und er liefert mir das gewünschte Ergebnis.
Beim asynchronen Aufruf des WS1 dagegen liefert er mir beim Aufruf einer WS2-Methode eine Fehlermeldung "Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt."

Dazu muss man sagen, dass der WS2 ein Settings-Objekt benötigt, welches normalerweise beim Start einer "normalen" WS2-Anwendung in die Session geschrieben wird. Diese Zuweisung mache ich nun auch beim Start meiner WS1-Anwendung, damit das Settings-Objekt dann für den WS2 bereitsteht.
Wie es aussieht, geht aber diese Session im asynchronen WS1 beim Aufruf des WS2 verloren. Im synchronen WS1, bei dem sonst alles gleich ist, funktioniert es wie gesagt.

Gibt es dafür Ideen, wie man das lösen kann?

Gruß
Tilo