tags:

views:

1169

answers:

4

Hi,

there is a piece of code:

class WCFConsoleHostApp : IBank
{
    private static int _instanceCounter;

    public WCFConsoleHostApp ()
        {
        Interlocked.Increment(ref _instanceCounter);
        Console.WriteLine(string.Format("{0:T} Instance nr " + _instanceCounter + " created", DateTime.Now));
        }
    private static int amount;

    static void Main(string[] args)
    {            
        ServiceHost host = new ServiceHost(typeof(WCFConsoleHostApp));
        host.Open();
        Console.WriteLine("Host is running...");
        Console.ReadLine();
    }

    #region IBank Members

    BankOperationResult IBank.Put(int amount)
    {
        Console.WriteLine(string.Format("{0:00} {1}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread) + " Putting...");
        WCFConsoleHostApp.amount += amount;
        Thread.Sleep(20000);
        Console.WriteLine(string.Format("{0:00} {1}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread) + " Putting done");
        return new BankOperationResult { CurrentAmount = WCFConsoleHostApp.amount, Success = true };            
    }

    BankOperationResult IBank.Withdraw(int amount)
    {
        Console.WriteLine(string.Format("{0:00} {1}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread) + " Withdrawing...");
        WCFConsoleHostApp.amount -= amount;
        Thread.Sleep(20000);
        Console.WriteLine(string.Format("{0:00} {1}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread) + " Withdrawing done");
        return new BankOperationResult { CurrentAmount = WCFConsoleHostApp.amount, Success = true };
    }

    #endregion
}

My test client application calls that service in 50 threads (service is PerCall). What I found very disturbing is when I added Thread.Sleep(20000) WCF creates one service instance per second using different thread from pool.

When I remove Thread.Sleep(20000) 50 instances are instanciated straight away, and about 2-4 threads are used to do it - which in fact I consider normal.

Could somebody explain why when Thread.Sleep causes those funny delays in creating instances?

A: 

You're pausing your service - or simulating long running jobs. wcf simply creates more threads to handle other clients that wants to be serviced.

nos
Creating threads is so time consuming?
dragonfly
no, why do you ask ? You're blocking one thread with sleeping, so wcf have to create more to handle other requests
nos
I asked because subsequent instances of service objects are created with big delay.
dragonfly
A: 

I'm not 100% sure about this, but you may be running into throttling issues with your WCF service. Take a look at the Throttling section of this MSDN article. I hope this helps.

Matt Davis
Thanks. I had already set throttling values to bigger then number of clients I use, so it's not the issue.
dragonfly
+5  A: 

You're mixing up your actual service implementation (the implementation of your IBank interface), and your service host in one and the same class.

This is definitely NOT good practice.

By default, WCF will by design instantiate a new separate copy of your service implementation class for each request coming in. This makes writing the service much easier (no need to fuss with multi-threading - each request gets its own class).

BUT: you shouldn't mix that with the ServiceHost, since you really only need one service host instance to host a service class that can handle hundreds or thousands of requests.

So - create one class

class BankImplementation : IBank
{
    private static int _instanceCounter;

    BankOperationResult IBank.Put(int amount)
    {
        Console.WriteLine(string.Format("{0:00} {1}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread) + " Putting...");
        //WCFConsoleHostApp.amount += amount;
        Thread.Sleep(20000);
        Console.WriteLine(string.Format("{0:00} {1}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread) + " Putting done");
        return new BankOperationResult { CurrentAmount = WCFConsoleHostApp.amount, Success = true };            
    }

    BankOperationResult IBank.Withdraw(int amount)
    {
        Console.WriteLine(string.Format("{0:00} {1}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread) + " Withdrawing...");
        //WCFConsoleHostApp.amount -= amount;
        Thread.Sleep(20000);
        Console.WriteLine(string.Format("{0:00} {1}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread) + " Withdrawing done");
        return new BankOperationResult { CurrentAmount = WCFConsoleHostApp.amount, Success = true };
    }
}

for your service code, and then a separate one (possibly even in a separate project all together) for hosting your service code:

class WCFConsoleHostApp
{

    public WCFConsoleHostApp ()
    {
        Interlocked.Increment(ref _instanceCounter);
        Console.WriteLine(string.Format("{0:T} Instance nr " + _instanceCounter + " created", DateTime.Now));
    }

    static void Main(string[] args)
    {            
        ServiceHost host = new ServiceHost(typeof(BankImplementation));
        host.Open();
        Console.WriteLine("Host is running...");
        Console.ReadLine();

        host.Close();
    }
}

Now you get one instance of your WCFConsoleHostApp, which will spin up the WCF runtime at host.Open() and handle the requests by instantiating as many BankImplementation class instances as needed.

UPDATE: Well, a WCF service is also "throttled", e.g. you can tweak how many concurrent calls and instances there are. By default, you get 10 concurrent session and 16 concurrent calls. If your service is already handling 16 concurrent calls and those sleep for some time, no additional service instances will be creating and handled.

See this excellent blog post by Kenny Wolf on details about service throttling. You can tweak those maximums as you see fit.

marc_s
Thanks for suggesstion. It's just a sample application I wrote to come to grips with some WCF configuration options. Anyway, what do you think about my question? :)
dragonfly
As regards you refactoring, private static int _instanceCounter;should be in first class :) right?
dragonfly
+1  A: 

I don't know that this is correct, but...

It might be that you're running into ThreadPool behavior rather than WCF behavior. Since the threads are staying open, the behavior of the ThreadPool may be that it will spin up additional threads to handle queued up work over time, as it will normally try to keep the thread count down to conserve resources.

So, theoretically, WCF will then queue a work item for each of the requests, but since the threads are not released for twenty seconds, they don't get serviced (past the initial request, that is). The ThreadPool sees this after a second, creates a new thread, and steals some work from the existing queue. Repeat every second.

kyoryu
I sounds reasonable. However, if it was true it would slow down the application. Thread Pool mechanizm should speed up application... Imagine WCF Service that is used behind Web Application. Web Application sends hundreds of requests per second... Sometimes .NET exasperates me :/
dragonfly
This does appear to be the issue...at least in my case. I've got the same kind of delay in one of my server methods, and in my client I immediately spawn five ThreadPool workers inside of an infinite loop that I can re-iterate by pressing a key. On the first iteration, the threads appear to be created very slowly (1-2 per second). Once the first five workers get their responses from the server and subsequently terminate, I trigger the second iteration, and all five workers begin concurrently and get concurrent responses from the server.
Brian Cline