我的目标是实现一个异步自托管的WCF服务,它将在单个线程中运行所有请求,并充分利用新的C# 5异步特性。
我的服务器将是一个控制台应用程序,在其中我将设置一个SingleThreadSynchronizationContext,如此处所指定,创建并打开一个ServiceHost,然后运行SynchronizationContext,以便所有WCF请求都在同一线程中处理。
问题在于,尽管服务器能够成功地在同一线程中处理所有请求,但异步操作会阻塞执行并被序列化,而不是交错执行。
我准备了一个简化的示例来重现这个问题。
以下是我的服务合同(服务器和客户端相同):
服务实现如下(稍微简化,但最终实现可能访问数据库或异步调用其他服务):
当我执行这个例子时,我得到了以下结果:
客户端
从服务器日志中可以看到,请求已经被正确地发布在SynchronizationContext中。然而,从时间戳可以看出,第一个请求在第二个请求启动之前就已经完成了,这完全违背了异步服务器的目的。
为什么会发生这种情况?
实现WCF自托管异步服务器的正确方式是什么?
我认为问题在于SingleThreadSynchronizationContext,但我不知道如何以其他方式实现它。
我研究了这个主题,但我找不到更多有用的关于异步WCF服务托管的信息,特别是使用基于任务的模式。
我的服务器将是一个控制台应用程序,在其中我将设置一个SingleThreadSynchronizationContext,如此处所指定,创建并打开一个ServiceHost,然后运行SynchronizationContext,以便所有WCF请求都在同一线程中处理。
问题在于,尽管服务器能够成功地在同一线程中处理所有请求,但异步操作会阻塞执行并被序列化,而不是交错执行。
我准备了一个简化的示例来重现这个问题。
以下是我的服务合同(服务器和客户端相同):
[ServiceContract]
public interface IMessageService
{
[OperationContract]
Task<bool> Post(String message);
}
服务实现如下(稍微简化,但最终实现可能访问数据库或异步调用其他服务):
public class MessageService : IMessageService
{
public async Task<bool> Post(string message)
{
Console.WriteLine(string.Format("[Thread {0} start] {1}", Thread.CurrentThread.ManagedThreadId, message));
await Task.Delay(5000);
Console.WriteLine(string.Format("[Thread {0} end] {1}", Thread.CurrentThread.ManagedThreadId, message));
return true;
}
}
该服务托管在控制台应用程序中:
static void Main(string[] args)
{
var syncCtx = new SingleThreadSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(syncCtx);
using (ServiceHost serviceHost = new ServiceHost(typeof(MessageService)))
{
NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
serviceHost.AddServiceEndpoint(typeof(IMessageService), binding, address);
serviceHost.Open();
syncCtx.Run();
serviceHost.Close();
}
}
正如您所看到的,我首先设置了一个单线程的SynchronizationContext
。接着,我创建、配置并打开了一个ServiceHost。根据这篇文章,由于我在其创建之前设置了SynchronizationContext,ServiceHost
将捕获它,并且所有客户端请求都将被发布在SynchronizationContext
中。接下来,我在同一线程中启动了SingleThreadSynchronizationContext
。
我创建了一个测试客户端,以fire-and-forget方式调用服务器。
static void Main(string[] args)
{
EndpointAddress ep = new EndpointAddress(address);
NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
IMessageService channel = ChannelFactory<IMessageService>.CreateChannel(binding, ep);
using (channel as IDisposable)
{
while (true)
{
string message = Console.ReadLine();
channel.Post(message);
}
}
}
当我执行这个例子时,我得到了以下结果:
客户端
服务器
从服务器日志中可以看到,请求已经被正确地发布在SynchronizationContext中。然而,从时间戳可以看出,第一个请求在第二个请求启动之前就已经完成了,这完全违背了异步服务器的目的。
为什么会发生这种情况?
实现WCF自托管异步服务器的正确方式是什么?
我认为问题在于SingleThreadSynchronizationContext,但我不知道如何以其他方式实现它。
我研究了这个主题,但我找不到更多有用的关于异步WCF服务托管的信息,特别是使用基于任务的模式。
添加
这是我对SingleThreadedSinchronizationContext
的实现。基本上与article中的实现相同:
public sealed class SingleThreadSynchronizationContext
: SynchronizationContext
{
private readonly BlockingCollection<WorkItem> queue = new BlockingCollection<WorkItem>();
public override void Post(SendOrPostCallback d, object state)
{
this.queue.Add(new WorkItem(d, state));
}
public void Complete() {
this.queue.CompleteAdding();
}
public void Run(CancellationToken cancellation = default(CancellationToken))
{
WorkItem workItem;
while (this.queue.TryTake(out workItem, Timeout.Infinite, cancellation))
workItem.Action(workItem.State);
}
}
public class WorkItem
{
public SendOrPostCallback Action { get; set; }
public object State { get; set; }
public WorkItem(SendOrPostCallback action, object state)
{
this.Action = action;
this.State = state;
}
}