【问题标题】:Threads in WCF serviceWCF 服务中的线程
【发布时间】:2009-12-02 22:43:34
【问题描述】:

有一段代码:

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
}

我的测试客户端应用程序在 50 个线程中调用该服务(服务是 PerCall)。我发现非常令人不安的是,当我使用池中的不同线程添加 Thread.Sleep(20000) WCF 每秒创建一个服务实例时。

当我删除 Thread.Sleep(20000) 时,会立即实例化 50 个实例,并且使用大约 2-4 个线程来执行此操作 - 实际上我认为这很正常。

有人能解释一下为什么 Thread.Sleep 在创建实例时会导致这些有趣的延迟吗?

【问题讨论】:

  • 我知道这是一个完全不同的问题,但请确保您同步访问静态 amount 变量。你现在拥有的不是线程安全的。
  • 对,但我很懒...这不是重点 ;)

标签: wcf threadpool


【解决方案1】:

您正在将您的实际服务实现(您的 IBank 接口的实现)和您的服务主机混合在同一个类中。

这绝对是不是好的做法。

默认情况下,WCF 将根据设计为每个传入的请求实例化一个新的单独的服务实现类副本。这使得编写服务变得更加容易(无需对多线程大惊小怪 - 每个请求都有自己的类) .

但是:您不应该将它与 ServiceHost 混用,因为您实际上只需要一个服务主机实例来托管可以处理成百上千个请求的服务类。

所以 - 创建一个类

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 };
    }
}

用于您的服务代码,然后是一个单独的(甚至可能在一个单独的项目中)用于托管您的服务代码:

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();
    }
}

现在您获得了WCFConsoleHostApp 的一个实例,它将在host.Open() 启动WCF 运行时并通过根据需要实例化尽可能多的BankImplementation 类实例来处理请求。 p>

更新:好吧,WCF 服务也是“节流”的,例如您可以调整有多少并发调用和实例。默认情况下,您将获得 10 个并发会话和 16 个并发调用。如果您的服务已经在处理 16 个并发调用并且这些调用休眠了一段时间,则不会创建和处理额外的服务实例。

请参阅此出色的blog post by Kenny Wolf,了解有关服务限制的详细信息。您可以根据需要调整这些最大值。

【讨论】:

  • 感谢您的建议。这只是我为掌握一些 WCF 配置选项而编写的示例应用程序。不管怎样,你觉得我的问题怎么样? :)
  • 关于你的重构,private static int _instanceCounter;应该是头等舱:) 对吧?
  • 您链接的这篇出色的帖子是某个美食评论家的博客。虽然它可能与节流有关,但我怀疑它与 wcf 有任何关系。
【解决方案2】:

我不知道这是否正确,但是...

这可能是您遇到了 ThreadPool 行为而不是 WCF 行为。由于线程保持打开状态,ThreadPool 的行为可能是随着时间的推移它会启动额外的线程来处理排队的工作,因为它通常会尝试减少线程计数以节省资源。

因此,理论上,WCF 将为每个请求排队一个工作项,但由于线程在 20 秒内没有释放,因此它们不会得到服务(即在初始请求之后)。 ThreadPool 在一秒钟后看到这一点,创建一个新线程,并从现有队列中窃取一些工作。每秒重复一次。

【讨论】:

  • 我听起来很有道理。但是,如果这是真的,它会减慢应用程序的速度。线程池机制应该加速应用程序......想象一下在 Web 应用程序后面使用的 WCF 服务。 Web 应用程序每秒发送数百个请求...有时 .NET 让我恼火:/
  • 这似乎是个问题......至少在我的情况下。我的一个服务器方法也有同样的延迟,在我的客户端中,我立即在一个无限循环中生成了五个线程池工作程序,我可以通过按键重新迭代。在第一次迭代中,线程的创建似乎非常缓慢(每秒 1-2 个)。一旦前五个工作人员从服务器获得响应并随后终止,我将触发第二次迭代,所有五个工作人员同时开始并从服务器获得并发响应。
【解决方案3】:

您正在暂停服务 - 或模拟长时间运行的作业。 wcf 只是创建更多线程来处理其他需要服务的客户端。

【讨论】:

  • 不,你为什么问?您在睡眠中阻塞了一个线程,因此 wcf 必须创建更多线程来处理其他请求
  • 我问是因为服务对象的后续实例的创建延迟很大。
【解决方案4】:

我对此不是 100% 确定,但您可能会遇到 WCF 服务的限制问题。查看this MSDN article 的Throttling 部分。我希望这会有所帮助。

【讨论】:

  • 谢谢。我已经将限制值设置为大于我使用的客户端数量,所以这不是问题。
猜你喜欢
  • 2011-04-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多