Art*_*nes 8 c# wcf asynchronous servicehost async-await
我的目标是实现异步自托管WCF服务,该服务将在单个线程中运行所有请求并充分利用新的C#5异步功能.
我的服务器将是一个控制台应用程序中,我将设置一个SingleThreadSynchronizationContext,按指定位置,创建和打开的ServiceHost,然后运行SynchronizationContext,所以所有的WCF请求都在同一个线程来处理.
问题是,尽管服务器能够成功处理同一线程中的所有请求,但异步操作阻止执行并被序列化,而不是隔行扫描.
我准备了一个简化的样本来重现这个问题.
这是我的服务合同(服务器和客户端相同):
[ServiceContract]
public interface IMessageService
{
[OperationContract]
Task<bool> Post(String message);
}
Run Code Online (Sandbox Code Playgroud)
服务实现如下(它有点简化,但最终实现可以访问数据库,甚至以异步方式调用其他服务):
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;
}
}
Run Code Online (Sandbox Code Playgroud)
该服务托管在控制台应用程序中:
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();
}
}
Run Code Online (Sandbox Code Playgroud)
如您所见,我做的第一件事就是设置一个单线程SynchronizationContext.接下来,我创建,配置和打开ServiceHost.根据这篇文章,因为我在创建之前设置了SynchronizationContext,ServiceHost它将捕获它并且所有客户端请求都将发布在SynchronizationContext.在序列中,我SingleThreadSynchronizationContext在同一个线程中启动.
我创建了一个测试客户端,它将以一种"一劳永逸"的方式调用服务器.
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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
当我执行该示例时,我得到以下结果:
客户

服务器

客户端以最小间隔(<1s)发送消息.我希望服务器接收第一个调用并在其中运行SingleThreadSynchronizationContext(排队新的WorkItem.当await达到关键字时,SynchronizationContext将再次捕获,继续发布到它,并且该方法将在此时返回任务,释放在SynchronizationContext处理第二个请求(至少开始处理它).
正如您在服务器日志中的Thread ID所看到的那样,请求正在发布中SynchronizationContext.但是,查看时间戳,我们可以看到第一个请求在第二个请求开始之前完成,这完全违背了拥有异步服务器的目的.
为什么会这样?
实现WCF自托管异步服务器的正确方法是什么?
我认为问题在于SingleThreadSynchronizationContext,但我看不出如何以任何其他方式实现它.
我研究了这个主题,但是我找不到有关异步WCF服务托管的更多有用信息,特别是使用基于任务的模式.
加成
这是我的实施SingleThreadedSinchronizationContext.它与文章中的基本相同:
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;
}
}
Run Code Online (Sandbox Code Playgroud)
你需要申请ConcurrencyMode.Multiple.
这就是术语有点令人困惑的地方,因为在这种情况下,它实际上并不意味着MSDN文档所说的"多线程".这意味着并发.默认情况下(单一并发),WCF将延迟其他请求,直到原始操作完成,因此您需要指定多个并发以允许重叠(并发)请求.您SynchronizationContext仍然会保证只有一个线程会处理所有请求,因此它实际上并不是多线程的.它是单线程并发.
另外,您可能需要考虑SynchronizationContext具有更清晰的关闭语义的不同.将SingleThreadSynchronizationContext您目前正在使用将"夹关",如果你打电话Complete; 中的任何async方法都是await永远不会恢复的.
我有一种AsyncContext类型,可以更好地支持清洁停机.如果您安装Nito.AsyncEx NuGet包,您可以使用如下服务器代码:
static SynchronizationContext syncCtx;
static ServiceHost serviceHost;
static void Main(string[] args)
{
AsyncContext.Run(() =>
{
syncCtx = SynchronizationContext.Current;
syncCtx.OperationStarted();
serviceHost = new ServiceHost(typeof(MessageService));
Console.CancelKeyPress += Console_CancelKeyPress;
var binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
serviceHost.AddServiceEndpoint(typeof(IMessageService), binding, address);
serviceHost.Open();
});
}
static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
{
if (serviceHost != null)
{
serviceHost.BeginClose(_ => syncCtx.OperationCompleted(), null);
serviceHost = null;
}
if (e.SpecialKey == ConsoleSpecialKey.ControlC)
e.Cancel = true;
}
Run Code Online (Sandbox Code Playgroud)
这会将Ctrl-C转换为"软"退出,这意味着只要有客户端连接(或直到"关闭"超时),应用程序就会继续运行.在关闭期间,现有客户端连接可以发出新请求,但新客户端连接将被拒绝.
Ctrl-Break仍然是一个"硬"退出; 您无法在控制台主机中更改它.
| 归档时间: |
|
| 查看次数: |
3653 次 |
| 最近记录: |