[英]Async WCF self hosted service
My objective is to implement an asynchronous self hosted WCF service which will run all requests in a single thread and make full use of the new C# 5 async features. 我的目标是实现异步自托管WCF服务,该服务将在单个线程中运行所有请求并充分利用新的C#5异步功能。
My server will be a Console app, in which I will setup a SingleThreadSynchronizationContext
, as specified here , create and open a ServiceHost and then run the SynchronizationContext
, so all the WCF requests are handled in the same thread. 我的服务器将是一个控制台应用程序,我在其中将设置一个
SingleThreadSynchronizationContext
,按规定这里 ,创建和打开的ServiceHost ,然后运行SynchronizationContext
,所以所有的WCF请求都在同一个线程来处理。
The problem is that, though the server was able to successfully handle all requests in the same thread, async operations are blocking the execution and being serialized, instead of being interlaced. 问题是,尽管服务器能够成功处理同一线程中的所有请求,但异步操作阻止执行并被序列化,而不是隔行扫描。
I prepared a simplified sample that reproduces the issue. 我准备了一个简化的样本来重现这个问题。
Here is my service contract (the same for server and client): 这是我的服务合同(服务器和客户端相同):
[ServiceContract]
public interface IMessageService
{
[OperationContract]
Task<bool> Post(String message);
}
The service implementation is the following (it is a bit simplified, but the final implementation may access databases or even call other services in asynchronous fashion): 服务实现如下(它有点简化,但最终实现可以访问数据库,甚至以异步方式调用其他服务):
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;
}
}
The service is hosted in a Console application: 该服务托管在控制台应用程序中:
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();
}
}
As you can see, the first thing I do is to setup a single threaded SynchronizationContext
. 如您所见,我要做的第一件事是设置一个线程
SynchronizationContext
。 Following, I create, configure and open a ServiceHost. 接下来,我创建,配置和打开ServiceHost。 According to this article , as I've set the SynchronizationContext prior to its creation, the
ServiceHost
will capture it and all the client requests will be posted in the SynchronizationContext
. 根据这篇文章 ,因为我在创建之前设置了SynchronizationContext,
ServiceHost
将捕获它,并且所有客户端请求将在SynchronizationContext
发布。 In the sequence, I start the SingleThreadSynchronizationContext
in the same thread. 在序列中,我在同一个线程中启动
SingleThreadSynchronizationContext
。
I created a test client that will call the server in a fire-and-forget fashion. 我创建了一个测试客户端,它将以一种“一劳永逸”的方式调用服务器。
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);
}
}
}
When I execute the example, I get the following results: 当我执行该示例时,我得到以下结果:
Client 客户
Server 服务器
The messages are sent by the client with a minimal interval ( < 1s). 客户端以最小间隔(<1s)发送消息。 I expected the server would receive the first call and run it in the
SingleThreadSynchronizationContext
(queueing a new WorkItem
. When the await
keyword was reached, the SynchronizationContext
would be once again captured, the continuation posted to it, and the method would return a Task at this point, freeing the SynchronizationContext
to deal with the second request (at least start dealing with it). 我希望服务器能够接收第一个调用并在
SingleThreadSynchronizationContext
运行它(对一个新的WorkItem
排队。当达到await
关键字时,将再次捕获SynchronizationContext
,向其发送延续,并且该方法将返回一个Task at at这一点,释放SynchronizationContext
来处理第二个请求(至少开始处理它)。
As you can see by the Thread's id in the server log, the requests are being correctly posted in the SynchronizationContext
. 正如您在服务器日志中的Thread ID所看到的,请求正在
SynchronizationContext
正确发布。 However, looking at the timestamps, we can see that the first request is being completed before the second is started, what totally defeats the purpose of having a async server. 但是,查看时间戳,我们可以看到第一个请求在第二个请求开始之前完成,这完全违背了拥有异步服务器的目的。
Why is that happening? 为什么会这样?
What is the correct way of implementing a WCF self hosted async server? 实现WCF自托管异步服务器的正确方法是什么?
I think the problem is with the SingleThreadSynchronizationContext, but I can't see how to implement it in any other manner. 我认为问题在于SingleThreadSynchronizationContext,但我看不出如何以任何其他方式实现它。
I researched the subject, but I could not find more useful information on asynchronous WCF service hosting, especially using the Task based pattern. 我研究了这个主题,但是我找不到有关异步WCF服务托管的更多有用信息,特别是使用基于任务的模式。
ADDITION 加成
Here is my implementation of the SingleThreadedSinchronizationContext
. 这是我对
SingleThreadedSinchronizationContext
实现。 It is basically the same as the one in the 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;
}
}
You need to apply ConcurrencyMode.Multiple
. 您需要应用
ConcurrencyMode.Multiple
。
This is where the terminology gets a bit confusing, because in this case it doesn't actually mean "multi-threaded" as the MSDN docs state. 这就是术语有点令人困惑的地方,因为在这种情况下,它实际上并不意味着MSDN文档所说的“多线程”。 It means concurrent .
这意味着并发 。 By default (single concurrency), WCF will delay other requests until the original operation has completed , so you need to specify multiple concurrency to permit overlapping (concurrent) requests.
默认情况下(单一并发),WCF将延迟其他请求,直到原始操作完成 ,因此您需要指定多个并发以允许重叠(并发)请求。 Your
SynchronizationContext
will still guarantee only a single thread will process all the requests, so it's not actually multi-threading. 您的
SynchronizationContext
仍然只保证一个线程将处理所有请求,因此它实际上不是多线程的。 It's single-threaded concurrency. 它是单线程并发。
On a side note, you might want to consider a different SynchronizationContext
that has cleaner shutdown semantics. 另外,您可能需要考虑具有更清晰的关闭语义的不同
SynchronizationContext
。 The SingleThreadSynchronizationContext
you are currently using will "clamp shut" if you call Complete
; 如果您调用
Complete
,您当前使用的SingleThreadSynchronizationContext
将“钳制”; any async
methods that are in an await
are just never resumed. await
中的任何async
方法都永远不会恢复。
I have an AsyncContext
type that has better support for clean shutdowns. 我有一个
AsyncContext
类型 ,它更好地支持干净的关闭。 If you install the Nito.AsyncEx NuGet package, you can use server code like this: 如果您安装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;
}
This will translate Ctrl-C into a "soft" exit, meaning the application will continue running as long as there are client connections (or until the "close" times out). 这会将Ctrl-C转换为“软”退出,这意味着只要有客户端连接(或直到“关闭”超时),应用程序就会继续运行。 During the close, existing client connections can make new requests, but new client connections will be rejected.
在关闭期间,现有客户端连接可以发出新请求,但新客户端连接将被拒绝。
Ctrl-Break is still a "hard" exit; Ctrl-Break仍然是一个“硬”退出; there's nothing you can do to change that in a Console host.
您无法在控制台主机中更改它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.