You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WCF ConcurrencyMode.Multiple服务为何需先发首请求才支持并发?

WCF并发处理异常问题解析

我搭建了一个WCF服务,核心配置如下:

  • 并发模式:ConcurrencyMode = Multiple
  • 绑定方式:WSHttpBinding
  • 实例上下文模式:InstanceContextMode.PerSession

服务端代码

[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.PerSession)]
public class StudentService : IStudentService
{
    public string GetStudentInfo(int studentId)
    {
        Console.WriteLine(string.Format("{0} - [GetStudentInfo] Receive request, thread id {1}", DateTime.Now, System.Environment.CurrentManagedThreadId));

        Thread.Sleep(5000); // 模拟业务耗时

        string studentName = string.Empty;
        switch (studentId)
        {
            case 1:
                studentName = "Johnson";
                break;
            case 2:
                studentName = "Henry";
                break;
            default:
                studentName = "No student found";
                break;
        }

        Console.WriteLine(string.Format("{0} - [GetStudentInfo] Leave request, thread id {1}", DateTime.Now, System.Environment.CurrentManagedThreadId));
        
        return studentName;
    }
}

现象描述

通过客户端多线程发送请求时,日志显示服务会顺序处理所有请求;但如果先发送一次请求,后续的多线程请求就能被并发处理。

客户端初始代码(顺序处理版本)

public static T CreateChannelFactory<T>()
{
    try
    {
        ChannelFactory<T> myChannelFactory = new ChannelFactory<T>(new WSHttpBinding());
        myChannelFactory.Endpoint.Address = new EndpointAddress("http://localhost:9091/StudentService");

        T channel = myChannelFactory.CreateChannel();

        return channel;
    }
    catch (Exception ex)
    {
        throw ex;
    }

}

static void Main(string[] args)
{
    StudentService.IStudentService channel = CreateChannelFactory<StudentService.IStudentService>();

    Thread[] arr = new Thread[10];
    for (int i = 0; i < arr.Length; i++)
    {
        arr[i] = new Thread(new ThreadStart(() => channel.GetStudentInfo(1)));
        
    }

    for (int i = 0; i < arr.Length; i++)
    {
        arr[i].Start();
    }

    for (int i = 0; i < arr.Length; i++)
    {
        arr[i].Join();
    }

    Console.ReadKey();
}

修改后客户端代码(并发处理版本)

static void Main(string[] args)
{
    StudentService.IStudentService channel = CreateChannelFactory<StudentService.IStudentService>();
    channel.GetStudentInfo(1); // 提前发送一次请求

    Thread[] arr = new Thread[10];
    for (int i = 0; i < arr.Length; i++)
    {
        arr[i] = new Thread(new ThreadStart(() => channel.GetStudentInfo(1)));
        
    }

    for (int i = 0; i < arr.Length; i++)
    {
        arr[i].Start();
    }

    for (int i = 0; i < arr.Length; i++)
    {
        arr[i].Join();
    }

    Console.ReadKey();
}

服务宿主代码

static void Main(string[] args)
{
    ServiceHost studentServiceHost = null;
    try
    {
        Uri httpBaseAddress = new Uri("http://localhost:9091/StudentService");

        studentServiceHost = new ServiceHost(typeof(StudentService.StudentService),
            httpBaseAddress);

        studentServiceHost.AddServiceEndpoint(typeof(StudentService.IStudentService),
                                        new WSHttpBinding(), "");

        ServiceMetadataBehavior serviceBehavior = new ServiceMetadataBehavior();
        serviceBehavior.HttpGetEnabled = true;
        studentServiceHost.Description.Behaviors.Add(serviceBehavior);

        studentServiceHost.Open();
        Console.WriteLine("Service is live now at : {0}", httpBaseAddress);
        Console.ReadKey();
        studentServiceHost.Close();

    }
    catch (Exception ex)
    {
        studentServiceHost = null;
        Console.WriteLine("There is an issue with StudentService:" + ex.Message);
    }
}

通道工厂创建代码

public static T CreateChannelFactory<T>()
{
    try
    {
        ChannelFactory<T> myChannelFactory = new ChannelFactory<T>(new WSHttpBinding());
        myChannelFactory.Endpoint.Address = new EndpointAddress("http://localhost:9091/StudentService");
        
        T channel = myChannelFactory.CreateChannel();

        return channel;
    }
    catch (Exception ex)
    {
        throw ex;
    }

}

问题

服务已配置ConcurrencyMode = Multiple,为何必须先发送一次请求,后续多线程请求才能被并发处理?

原因解析

核心原因在于WSHttpBinding的安全会话建立机制:

  1. WSHttpBinding默认启用SecurityMode.Message安全会话,会话需要通过第一次请求完成密钥协商、上下文同步等初始化流程。
  2. 初始客户端代码中,多线程同时调用通道方法时,会话尚未建立,所有请求会竞争触发会话建立逻辑——但WCF通道为保证会话建立的原子性,会自动序列化所有未在会话内的请求,只有当第一个请求完成会话建立后,后续请求才能进入服务实例的处理流程,因此表现为顺序执行。
  3. 提前发送一次请求后,客户端与服务端的安全会话已完全建立,通道进入就绪状态。此时ConcurrencyMode.Multiple的配置正式生效:同一个会话对应的服务实例允许被多个线程同时调用,后续多线程请求无需等待会话协商,直接在已建立的会话上并发处理,因此呈现并发执行的效果。

补充说明:InstanceContextMode.PerSession意味着每个客户端会话对应一个独立服务实例,ConcurrencyMode.Multiple允许该实例同时处理多个请求,但前提是会话已完成初始化,否则通道的请求序列化逻辑会优先保障会话建立的正确性。


内容的提问来源于stack exchange,提问作者Hau Vo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 20:23:11