gpt4 book ai didi

c# - 异步 WCF 自托管服务

转载 作者:太空狗 更新时间:2023-10-29 18:29:21 30 4
gpt4 key购买 nike

我的目标是实现一个异步自托管 WCF 服务,该服务将在单个线程中运行所有请求并充分利用新的 C# 5 异步功能。

我的服务器将是一个控制台应用程序,我将在其中设置一个 SingleThreadSynchronizationContext,如指定的 here , 创建并打开一个 ServiceHost然后运行 ​​SynchronizationContext,因此所有 WCF 请求都在同一个线程中处理。

问题在于,尽管服务器能够在同一线程中成功处理所有请求,异步操作会阻塞执行并被序列化,而不是交错进行。

我准备了一个重现该问题的简化示例。

这是我的服务契约(Contract)(服务端和客户端都一样):

[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。根据this article ,因为我在创建 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);
}
}
}

当我执行该示例时,我得到以下结果:

客户端

enter image description here

服务器

enter image description here

消息由客户端以最小间隔(< 1 秒)发送。我预计服务器会收到第一个调用并在 SingleThreadSynchronizationContext 中运行它(排队一个新的 WorkItem。当到达 await 关键字时, SynchronizationContext 将再次被捕获,继续发送给它,此时该方法将返回一个任务,释放 SynchronizationContext 来处理第二个请求(至少开始处理它)。

正如您在服务器日志中的线程 ID 所见,请求已正确发布到 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;
}
}

最佳答案

您需要应用ConcurrencyMode.Multiple

这就是术语变得有点困惑的地方,因为在这种情况下,它实际上并不像 MSDN 文档所述那样表示“多线程”。这意味着并发。默认情况下(单并发),WCF 将延迟其他请求,直到原始操作完成,因此您需要指定多并发以允许重叠(并发)请求。您的 SynchronizationContext 仍将保证只有一个线程会处理所有请求,因此它不是实际上多线程。它是单线程并发。

附带说明一下,您可能想要考虑一个不同的 SynchronizationContext,它具有更清晰的关闭语义。如果您调用 Complete,您当前使用的 SingleThreadSynchronizationContext 将“关闭”; await 中的任何 async 方法都不会恢复。

我有一个 AsyncContext type更好地支持干净关闭。如果你安装了 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;
}

这会将 Ctrl-C 转换为“软”退出,这意味着只要有客户端连接,应用程序就会继续运行(或直到“关闭”超时)。在关闭期间,现有的客户端连接可以发出新的请求,但新的客户端连接将被拒绝。

Ctrl-Break 仍然是一个“硬”退出;您无法在控制台主机中更改它。

关于c# - 异步 WCF 自托管服务,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17080037/

30 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com