Dienstag, 18. März 2014

Implementierung eines asynchronen WcfProxy

Vor einiger Zeit zeigte mir ein Kollege eine interessante Implementierung für einen Wcf Client Proxy, die er im Buch Professionell entwickeln mit Visual C# 2012: Das Praxisbuch. von Matthias Geirhos gefunden hat. Ziel war es das Verbindungsmanagement und ExceptionHandling an einer zentralen Stelle (einer statischen Klasse) abzuwickeln.

Leider war die Implementierung speziell für die synchronen Methodenaufrufe über den WebService gedacht, aus diesem Grund habe ich eine TAP-variante implementiert, die  mit async und await arbeitet:
Bitte bedenkt, dass dieser Code nur als Ansatz zu verstehen ist, hier fehlt noch einiges (Exception Handling, Logging, Kann man wirklich con.Close() aufrufen (?), retry connect,...)

    public static class WcfProxy
    {
        public static async Task CallAsync<T>(Action<T> webServiceMethod) where T : ICommunicationObject, new()
        {
            T con = new T();
            try
            {
                // open the connection
                await Task.Factory.FromAsync(con.BeginOpen, con.EndOpen, null);

                // call the method
                await Task.Factory.FromAsync<T>(webServiceMethod.BeginInvoke, webServiceMethod.EndInvoke, con, null);
            }
            catch (CommunicationException comEx)
            {
                // .. do something .. //
                System.Diagnostics.Debug.WriteLine(comEx.ToString());
            }
            finally
            {
                con.Close();
            }
        }

        public static async Task<TResult> CallAsync<T, TResult>(Func<T, TResult> webServiceFunction) where T : ICommunicationObject, new() where TResult : class
        {
            T con = new T();
            TResult res = null;
            try
            {
                // open the connection
                await Task.Factory.FromAsync(con.BeginOpen, con.EndOpen, null);

                // call the method
                res = await Task.Factory.FromAsync<T, TResult>(webServiceFunction.BeginInvoke, webServiceFunction.EndInvoke, con, null);
            }
            catch (CommunicationException comEx)
            {
                // .. do something .. //
                System.Diagnostics.Debug.WriteLine(comEx.ToString());
            }
            finally
            {
                con.Close();
            }
            return res;
        }

    }

Montag, 17. März 2014

Async Methoden im WCF Stub in Portable Class Library (PCL)

Vorbetrachtung

Wenn man mit dem Tool svcutil.exe einen Webservice Stub erstellt, gibt es (wenn man es genau nimmt) 3 verschiedene Arten, wie eine Methode erstellt werden kann:
  1. als synchroner Auftruf, da entspricht der Methodenname dem Namen im WCF-ServiceContract
  2. als asynchroner Aufruf im EAP-Pattern (Event-based Asynchronous Pattern)
    Hierbei gibt es zum einen eine Methode mit dem Suffix -Async, und ein event mit dem Methodennamen und dem Suffix -Completed, außerdem gibt es für jedes Ergebnis einen eigenen EventArgs-Typ 
  3. als asynchroner Aufruf im TAP-Pattern (Task-based Asynchronous Pattern)
    Hier wird auch der Methode ein -Async angehängt, jedoch ist das Ergebnis ein Task mit einem Resulttyp, der dem des Webservice-Result entspricht. Ist die Methode void, gibt es natürlich auch keinen Resulttyp. Der Vorteil dieser Methode ist, dass man kein umständliches Event-Housekeeping machen muss, sondern die Methode fast wie eine synchrone Methode aufrufen kann, ohne dass die Anwendung einfriert

Liest man sich die Beschreibungen durch, erkennt man sicher meine Vorliebe für die TAP-Variante. Diese Variante wird auch ab dem Zielframework .NET 4.5 standardmäßig anstelle von EAP erzeugt. Das ist auch gut so, aber an der Überschrift erkennt man, dass es wohl nicht immer so ist, denn in einer PCL-Bibliothek, die verschiedene Zielplattformen haben kann, wird automatisch Variante 2 erzeugt, was sehr unschön ist, denn für EAP muss man nicht nur unmengen mehr Code schreiben, sondern bei mehreren parallelen Aufrufen auch noch Housekeeping mit State-Objekten

Wenn svcutil.exe uns hier nicht helfen will, müssen wir uns eben selbst helfen:
Nehmen wir folgende WebService:
    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        Person GetNextInterviewPartner();
      
        [OperationContract]
        Person GetNextInterviewPartnerGeneric(string typeFullName);

        [OperationContract]
        void AddPersonToList(Person person);
    }

Unabhängig von der Implementierung, oder was der Service eigentlich macht, erhalten wir folgenden Stub (gekürzt):
 public partial class Service1Client {
        
        public void GetNextInterviewPartnerAsync()
        {...}
        
        public System.IAsyncResult BeginGetNextInterviewPartner(System.AsyncCallback callback, object asyncState)
        {...}
        
        public object EndGetNextInterviewPartner(System.IAsyncResult result)
        {...}
        
        public void GetNextInterviewPartnerGenericAsync(string typeFullName)
        {...}
        
        public System.IAsyncResult BeginGetNextInterviewPartnerGeneric(string typeFullName, System.AsyncCallback callback, object asyncState)
        {...}
        
        public object EndGetNextInterviewPartnerGeneric(System.IAsyncResult result)
        {...}
        
        public void AddPersonToListAsync(object person)
        {...}
        
        public System.IAsyncResult BeginAddPersonToList(object person, System.AsyncCallback callback, object asyncState)
        {...}
        
        public void EndAddPersonToList(System.IAsyncResult result)
        {...}

    //[...]
    }

Ich habe den Stub stark gekürzt, und nur die fürs Weitere wichtigen Methoden herausgestellt. Die Events, die bei der standardmäßigen Implementierung notwendig sind, werden nicht benötigt. Auf Basis dieses Stubs kann man einen Proxy implementieren, der auf das TAP-Pattern aufbaut. Dabei hilft die Funktion Task.
  public class Service1ClientTAP : Service1Client
    {
        public new Task<object> GetNextInterviewPartnerAsync()
        {
            return Task.Factory.FromAsync<object>(BeginGetNextInterviewPartner, EndGetNextInterviewPartner, TaskCreationOptions.PreferFairness);
        }

        public new Task<object> GetNextInterviewPartnerGenericAsync(string typeFullName)
        {
            return Task.Factory.FromAsync<string,object>(BeginGetNextInterviewPartnerGeneric, EndGetNextInterviewPartnerGeneric, typeFullName,  TaskCreationOptions.PreferFairness);
        }

        public new Task AddPersonToListAsync(object person)
        {
            return Task.Factory.FromAsync<object>(BeginAddPersonToList, EndAddPersonToList, person, TaskCreationOptions.PreferFairness);
        }
    }
Das Wichtige sind die beiden delegates Begin... und End..., diese werden in der Funktion Task.Factory.FromAsync als erstes übergeben, danach können bis zu 3 Übergabeparameter folgen. Danach werden die TaskCreationOptions übergeben. Normalerweise muss die Methode nicht explizit typisiert werden, wenn es sich bei den Parametern und Resultaten nicht unbedingt um object-Typen handelt. Hier nochmal zusammengefasst, wie die einzelnen Funktionssignaturen überführt werden:
1. TResult TestFunktion1(T1 parameter1, T2 parameter2, T3 parameter3)
2. TResult TestFunktion2()
3. void TestFunktion3(T1 parameter1, T2 parameter2, T3 parameter3)
// 1.
public Task<TResult> TestFunktion1Async(T1 parameter1, T2 parameter2, T3 parameter3)
{
   return Task.Factory.FromAsync<T1, T2, T3, TResult> (BeginTestFunktion1, EndTestFunktion2, parameter1, parameter2, parameter3, TaskCreationOptions.PreferFairness);
}

// 2.
public Task<TResult> TestFunktion2Async()
{
   return Task.Factory.FromAsync<TResult> (BeginTestFunktion1, EndTestFunktion2, TaskCreationOptions.PreferFairness);
}

// 3.
public Task TestFunktion3Async(T1 parameter1, T2 parameter2, T3 parameter3)
{
   return Task.Factory.FromAsync<T1, T2, T3> (BeginTestFunktion1, EndTestFunktion2, parameter1, parameter2, parameter3, TaskCreationOptions.PreferFairness);
}