Search code examples
c#wcfasync-await

Call asynchronous method on a WCF service with Single InstanceContextMode


I'm trying to call asynchronously a method on a WCF which has a single instanceContextMode.

Is there a way reuse an instance of a service while it is waiting for an asynchronous method ? I use the Task way for generating async operations on my WCF service reference.

I made a testing project because I had some problems on my application. My TestService expose 2 methods :

    - A fast method which should be called synchronously
    - A long method which should be called asynchronously

For some other reasons, my service should be in Single instanceContextMode:

[ServiceContract]
public interface ITestService
{
    [OperationContract]
    string FastMethod(string name);

    [OperationContract]
    Task<string> LongMethodAsync(string name);
}

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class TestService : ITestService
{
    public TestService() { }

    public string FastMethod(string name)
    {
        Console.WriteLine($"{DateTime.Now.ToLongTimeString()} - FastMethod call - {name}");
        return $"FastMethod - {name}";
    }

    public async Task<string> LongMethodAsync(string name)
    {
        for (int i = 5; i > 0; i--)
        {
            await Task.Delay(1000);
            Console.WriteLine($"LongMethod pending {i}");
        }

        Console.WriteLine($"{DateTime.Now.ToLongTimeString()} - LongMethod call - {name}");
        return $"LongMethod - {name}";
    }
}

My host is a simple console application which allow me to see WS calls through Console.WriteLine() methods:

class Program
{
    static void Main(string[] args)
    {
        using (ServiceHost hostTest = new ServiceHost(typeof(TestService)))
        {
            Console.WriteLine($"{DateTime.Now.ToLongTimeString()} - Service starting...");
            hostTest.Open();
            Console.WriteLine($"{DateTime.Now.ToLongTimeString()} - Service started");
            Console.ReadKey();
            hostTest.Close();
        }
    }
}

On my client side, I only have a simple form which show result calls:

private async void button1_Click(object sender, EventArgs e)
{
    string result;
    result = srvClient.FastMethod("test1");
    resultTextBox.Text = $"{DateTime.Now.ToLongTimeString()} - {result}";

    Task<string> t1 = srvClient.LongMethodAsync("test2");

    result = srvClient.FastMethod("test3");
    resultTextBox.Text += $"\r\n{DateTime.Now.ToLongTimeString()} - {result}";

    System.Threading.Thread.Sleep(1000);
    result = srvClient.FastMethod("test4");
    resultTextBox.Text += $"\r\n{DateTime.Now.ToLongTimeString()} - {result}";

    result = await t1;
    resultTextBox.Text += $"\r\n{DateTime.Now.ToLongTimeString()} - {result}";
}

When I do that I can see in my resultTestBox and in the host Console that "test3" and "test4" are called only after the end of "test2".

If I do the same test locally (not through a WCF service) the behavoir is like expected, the "test3" and "test4" are called while "test2" is waiting.


Solution

  • According to MSDN:

    If the InstanceContextMode value is set to Single the result is that your service can only process one message at a time unless you also set the ConcurrencyMode value to ConcurrencyMode.

    (looks like they forgot to tell what ConcurrencyMode)

    So just set the right ConcurrencyMode on your service:

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
    

    Make sure your code is stateless and thread-safe though. This combination is very error prone.