C# - WebService függvényeinek aszinkron hívása

forráskód letöltése
Ha meghívjuk egy Web Sercive alkalmazás valamely függvényét, akkor saját alkalmazásunk ennél a pontnál megáll, „lefagy” mindaddig, amíg nem kap választ a Web Service-től. Ha ez a Web Service nem azon a gépen fut, ahol a saját alkalmazásunk, akkor a válasz idő több másodperc is lehet és így a felhasználó tényleg azt hiheti, hogy az alkalmazásunk lefagyott, mivel nem reagál semmire. Ezt elkerülendő célszerű lenne a Web Service függvényét egy külön szálon futtatva meghívni, így nem okozna problémát, hogy az eredmény csak több másodperc múlva jelenik meg.
Szerencsére még e külön szál leprogramozásával sem kell foglalkoznunk, mert a Visual Studio.NET erre is kínál egy igen egyszerű megoldást.
Amikor a WSDL.EXE segéd programmal éppen létrehozunk egy Web Service-hez tartozó proxy osztályt, akkor ebbe rögtön létrehozásra kerülnek olyan függvények is, melyekkel megvalósíthatjuk az aszinkron hívást. Ha például készítünk egy olyan Web Service-t, mely tartalmaz egy Add nevű függvényt, mely két paraméterként kapott szám összegét adja eredményül, majd használjuk a WSDL.EXE-t és megnézzük az elkészült forráskódot, akkor az alábbiakat láthatjuk.
Adott lesz maga az Add függvény meghívását szolgáló függvény:
    public string Add(int a, int b) {
        object[] results = this.Invoke("Add", new object[] {
                    a,
                    b});
        return ((string)(results[0]));
    }
Ezenkívül kapunk még két másik függvényt is, melyeknek nevei úgy lesznek kialakítva, hogy az előző Add függvénynév elé kerül a Begin, illetve az End szó.
    public System.IAsyncResult BeginAdd(int a, int b, System.AsyncCallback callback, object asyncState) {
        return this.BeginInvoke("Add", new object[] {
                    a,
                    b}, callback, asyncState);
    }
    public string EndAdd(System.IAsyncResult asyncResult) {
        object[] results = this.EndInvoke(asyncResult);
        return ((string)(results[0]));
    }
E két függvénnyel tudjuk majd megvalósítani a külön szálon történő függvényhívást. Megfigyelhetjük, hogy a BeginAdd függvény paraméter listája valamelyest különbözik az Add függvényétől. A paraméter lista úgy alakul a Begin kezdetű függvények esetén, hogy először jönnek az eredeti függvény paraméterei, majd ezt követi két olyan paraméter, mely aszinkron híváshoz szükséges. Ezekre alább még visszatérünk. A függvény visszatérési értéke egy IAsyncResult interfész lesz. Ennek értékére a későbbiekben lesz szükségünk, amikor az End kezdetű függvényt hívjuk. Ennek egyetlen paraméterében kell majd megadnunk ezt az értéket. Az End kezdetű függvény visszatérési értéke egyezik az eredeti függvény visszatérési értékével.
Ha tehát aszinkron módon szeretnénk meghívni fenti példa Add függvényét, akkor a BeginAdd hívásával tudjuk ezt a hívást kezdeményezni. Ekkor egy új szálon elindul a kérés és alkalmazásunk folytatódik azonnal, nem fagy le. Az EndAdd hívásával pedig lekérdezhetjük a hívott Web Service függvényének eredményét.
Nézzük miként is megy ez a gyakorlatban.
Első lépésként készítsünk egy Web Service-t, melyben a fenti Add függvényt hozzuk létre. Mellékelt példában AsyncService projektben tettük ezt meg.
    [WebMethod]
    public string Add(int a, int b)
    {
      for (int i=0; i<1000000000; i++)
      {         
      }
      return (a+b).ToString();
    }   
Az egyszerű összeadás elé beillesztettünk egy ciklust, mely csupán az időt húzza. Erre azért van szükségünk, hogy szimuláljuk azt az esetet, hogy az interneten keresztül elért Web Service válaszára néhány másodpercet várnia kelljen alkalmazásunknál. Az Ön gépének sebességétől függően a ciklus számlálóját csökkentse, vagy növelje.
Az Add függvény tehát összeadja a két paraméterként kapott számot, majd ezt sztringként visszaadja.
Nézzük most a kliens alkalmazást, melyben felhasználjuk ezt a Web Service-t. Ez lesz a TestApp nevű projekt.
Az új projekt létrehozása után szükséges néhány előkészítő lépés:
A web service használatához a System.Web.Services névtérre szükségünk lesz. Ehhez válasszuk a Project - Add Reference menüpontot a .NET lapon keressük elő ezt és adjuk a projektünkhöz.
Ahhoz, hogy egyszerűen elérhessük ezt a web service-t és felhasználhassuk alkalmazásunkban, szükséges hogy egy ún. proxy objektumot generáltassunk. Ehhez a Visual Studio-hoz mellékelt WSDL.EXE nevű segédprogram használata szükséges. Nyissunk egy parancssort (Visual Studio.NET Command Prompt!) majd írjuk a következőt:
Wsdl http://localhost/AsyncService/Service1.asmx?WSDL
Értelemszerűen a localhost helyére annak a szervernek a címe kerül, ahol a web service elérhető.
A WSDL futása során generál számunkra egy Service1.cs forráskódot, mely tartalmaz egy olyan objektumot, amelyből példányt létrehozva az adott web service-t használhatjuk úgy, mintha az egy egyszerű osztály lenne, amelyet a saját alkalmazásunkban hoztunk létre.
Ezek után három lehetőségünk van az Add függvény meghívására.
1. Szinkron hívás
Szinkron hívással egyszerűen meghívjuk az Add függvényt. Ekkor a programunk futása megszakad és csak akkor folytatódik, ha a már a Web Service válasza megérkezett.
    private void button1_Click(object sender, System.EventArgs e)
    {
      Service1 s = new Service1();
      label1.Text = s.Add(10, 20);
    }
2. Aszinkron hívás
Ha a Web Service hívását külön szálon szeretnénk futtatni, akkor hívjuk meg a BeginAdd függvényt, most a két új paraméterre nem lesz szükségünk, így ott null értéket adunk át.
    private void button2_Click(object sender, System.EventArgs e)
    {
      s1 = new Service1();
      ar = s1.BeginAdd(10, 20, null, null);
    }
Ezzel a kérés elindult a Web Service felé, most már csak az a kérdés, hogy mikor jön meg a válasz. Ennek lekérdezésére a BeginAdd által adott IAsyncResult interfész IsCompleted nevű property-e ad választ. Ha ennek értéke igaz, akkor a válasz már megérkezett a Web Service-től, így lekérdezhetjük azt.
    private void button3_Click(object sender, System.EventArgs e)
    {
      label3.Text = ar.IsCompleted.ToString();
    }
A Web Service által visszaadott értéket az EndAdd függvénnyel kérdezhetjük le. Itt paraméterként az IAsyncResult interfészt kell megadnunk, melyet a BeginAdd adott vissza. Az EndAdd visszatérési értéke egyezik az Add függvényével, így ebből megtudhatjuk az eredményt.
    private void button4_Click(object sender, System.EventArgs e)
    {
      if (ar.IsCompleted)
      {
        label4.Text = s1.EndAdd(ar);
      }
    }
3. Aszinkron hívás callback függvénnyel
Aszinkron hívásra van egy olyan lehetőségünk is, hogy a Begin kezdetű függvényeknél megadunk egy callback függvényt, mely akkor kerül meghívásra, ha a Web Service válaszolt. Így nem kell alkalmazásunknak folyamatosan ellenőrizgetni, hogy a válasz megérkezett-e már, hiszen a megadott callback függvény azonnal meghívásra kerül, ha ez megtörtént.
Ehhez készítenünk kell egy függvényt, melyet CallbackFunction-nak neveztünk el. A függvény paraméter listája kötött: egy darab IAsyncResult típusú paramétert kell tartalmazni és nem rendelkezhet visszatérési értékkel. Amikor a függvény meghívásra kerül, akkor biztosak lehetünk abban, hogy a Web Service válaszolt, így az EndAdd függvényt meghívhatjuk, hogy kiolvassuk az eredményt.
    private void CallbackFunction(IAsyncResult ar)
    {
      label5.Text = s2.EndAdd(ar);
    }
A kérés elindítása most annyiban módosul, hogy létre kell hoznunk egy AsyncCallback típusú változót, melyet a BeginAdd függvénynek kell megadnunk.
    private void button5_Click(object sender, System.EventArgs e)
    {
      s2 = new Service1();
      AsyncCallback ac = new AsyncCallback(CallbackFunction);
      s2.BeginAdd(10, 20, ac, null);
    }