Asynchronous methods in C# using generics


Asynchronous methods provide a means to execute long running operations in parallel. One strategy for implementing these is to define generic methods to handle the asynchronous actions and pass the methods to be called asynchronously as parameters. A simple example follows.

The generic support methods

A class is created with static methods to support start and completion of asynchronous methods. The support methods for a single parameter with return type are shown. Other methods can be added to handle other delegate signature requirements.

    // Parameter and return type async method helper
    public static IAsyncResult BeginAsync<T1, TResult>(Func<T1, TResult> method,
                                                       T1 parameter)
    {
        var methodCopy = method;
        if (methodCopy == null)
            throw new ArgumentNullException();

        // Start the asynchronous operation
        // No callback or state object
        return methodCopy.BeginInvoke(parameter, null, null);
    }


    // Parameter and return type async method helper - async done
    public static TResult EndAsync<T1, TResult>(IAsyncResult aResult)
    {
        Func<T1, TResult> method =
            (Func<T1, TResult>) ((AsyncResult) aResult).AsyncDelegate;

        // Retrieve the result
        return method.EndInvoke(aResult);
    }

Asynchronous running

With these methods in place it becomes a simple process to use the asynchronous operations in code. Each operation calls the appropriate generic method to invoke the function, and a generic method to retrieve the result. A blocking strategy is shown here (so not applicable for use in a UI thread) but the example can easily be modified to use callbacks on completion.

    private int LongComputation(String data)
    {
        // Some long operation
        Thread.Sleep(10000);

        return data.Length;
    }

    private int MultiComputation()
    {
        IAsyncResult aRes1 =
            AsyncMethods.BeginAsync<String, int>(LongComputation, "Visions");
        IAsyncResult aRes2 =
            AsyncMethods.BeginAsync<String, int>(LongComputation, "of");
        IAsyncResult aRes3 =
            AsyncMethods.BeginAsync<String, int>(LongComputation, "Software");

        // Blocks for async call completion - not for use on UI thread
        int result = AsyncMethods.EndAsync<String, int>(aRes1);
        result += AsyncMethods.EndAsync<String, int>(aRes2);
        result += AsyncMethods.EndAsync<String, int>(aRes3);

        return result;
    }
Advertisement

WCF servers and clients without configuration files


WCF servers and clients can be set up using a declarative mechanism in web.config and app.config files. It is also possible to set up WCF servers and clients without using config files. A simple example follows.

For these examples the WCF server and client will be hosted in simple WPF applications.

WCF Server

A reference to the System.ServiceModel assembly / dll is added to link the associated namespaces. Then a ServiceContract is defined:

using System.ServiceModel;

...

[ServiceContract]
public interface IWCFContract
{
    [OperationContract]
    int Add(int value1, int value2);

    [OperationContract]
    int Multiply(int value1, int value2);
}

Next a concrete implementation of the service contract needs to be defined:

public class WCFConcrete : IWCFContract
{
    public int Add(int value1, int value2)
    {
        return (value1 + value2);
    }

    public int Multiply(int value1, int value2)
    {
        return (value1 * value2);
    }
}

Then the service host is programmatically created and the metadata is exposed and endpoints added:

using System.ServiceModel;
using System.ServiceModel.Description;

...

public ServiceHost CreateAndOpenHost()
{
    ServiceHost newHost =
        new ServiceHost(typeof(WCFConcrete),
                        new Uri("http://localhost:8002/SimpleService"));

    // Configure publication of service metadata
    ServiceMetadataBehavior behaviour = new ServiceMetadataBehavior();
    behaviour.HttpGetEnabled = true;
    behaviour.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
    newHost.Description.Behaviors.Add(behaviour);

    // Add a MEX (metadata exchange) endpoint
    newHost.AddServiceEndpoint(ServiceMetadataBehavior.MexContractName,
                               MetadataExchangeBindings.CreateMexHttpBinding(),
                               "mex");

    // Add an application endpoint using a WSHttpBinding
    newHost.AddServiceEndpoint(typeof(IWCFContract), new WSHttpBinding(), "");

    // Open the service host
    newHost.Open();

    return newHost;
}

Appropriate permissions will need to be given to the user running the application. This can be achieved by running the following in an elevated / administrator command window:

netsh http add urlacl url=http://+:8002/SimpleService user=DOMAIN\username

This service can now be run. The running service can be verified by browsing to the service in Internet Explorer:

http://localhost:8002/SimpleService

WCF Client

To create the client class run the svcutil.exe (located at C:\Program Files\Microsoft SDKs\Windows\{version}\Bin) on the wsdl for the service (while the service is running):

svcutil.exe http://localhost:8002/SimpleService?wsdl

This will create a WCFConcrete.cs (along with a config file which is not needed for this example.) This class file is added to the client project. Again a reference to the System.ServiceModel assembly will need to be added to the client project.

The ChannelFactory is then used to create a client to the service:

using System.ServiceModel;

...

public static IWCFContract CreateWCFClient(String endpointAddress)
{
    IWCFContract result = null;

    // Set up the binding and endpoint.
    WSHttpBinding binding = new WSHttpBinding();
    EndpointAddress endpoint = new EndpointAddress(endpointAddress);

    // Create a channel factory.
    ChannelFactory<IWCFContract> channelFactory =
                  new ChannelFactory<IWCFContract>(binding, endpoint);

    // Create a channel.
    result = channelFactory.CreateChannel();

    return result;
}

With this in place a client can simply be created and used to access the service (when it is running):

...

IWCFContract client = CreateWCFClient("http://localhost:8002/SimpleService");

int addResult = client.Add(2, 3);
int multiplyResult = client.Multiply(2, 3);