求WCF双工“TwoWay”订阅+回调示例
Posted
技术标签:
【中文标题】求WCF双工“TwoWay”订阅+回调示例【英文标题】:Seeking WCF Duplex "TwoWay" Subscribe+Callback Example 【发布时间】:2011-03-29 05:04:31 【问题描述】:再次更新赏金,因为我真的需要知道如何让它发挥作用,或者明确回答为什么它不会。
I've added an alternative explanation of the problem here.
让双向 (IsOneWay = false) WCF 客户端-服务器在 .Net 3/3.5 中工作真是太棒了。
客户端成功注册服务后,服务的周期性 Announcement() 回调到注册的客户端。现在是客户端或服务器挂起,直到服务器的 SendTimeout(调整为 2 秒)过去。那么服务器端出现超时异常如下。只有这样,客户端用户代码才会立即接收方法调用并尝试返回一个值。到那时,客户端的套接字被中止并且 WCF 的东西失败了。
在我看来,客户端上的某些东西正在将其本地 WCF 队列从处理中挂起,直到套接字超时,但还不足以取消本地方法调用。但是,如果可以相信以下异常,则服务器正试图向http://schemas.microsoft.com/2005/12/ServiceModel/Addressing/Anonymous 发送操作(不合适!)并且正在超时。也许该 URI 只是远程连接客户端的“名称”,因为 WCF 知道出于错误消息的目的引用它,它似乎只是意味着它无法加载 URI。分不清是服务端先失败还是客户端先失败。
我已尝试添加 WCF 跟踪,但没有获得更多信息。
Similar sample code is here,但肯定是太难消化了。我已经尝试过该代码的变体。
TimeoutException 'This request operation sent to http://schemas.microsoft.com/2005/12/ServiceModel/Addressing/Anonymous did not receive a reply within the configured timeout (00:00:00). The time allotted to this operation may have been a portion of a longer timeout. This may be because the service is still processing the operation or because the service was unable to send a reply message. Please consider increasing the operation timeout (by casting the channel/proxy to IContextChannel and setting the OperationTimeout property) and ensure that the service is able to connect to the client.'
Server stack trace:
at System.ServiceModel.Dispatcher.DuplexChannelBinder.SyncDuplexRequest.WaitForReply(TimeSpan timeout)
at System.ServiceModel.Dispatcher.DuplexChannelBinder.Request(Message message, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)
【问题讨论】:
这适用于 3.0/3.5,而不是 4.0。谢谢。 您是否在客户端和服务器上都使用了 wcf 跟踪来查看它是否显示了什么? 是的,尽我所能解释结果,我只发现与上面列出的相同的异常。否则,所有排序和消息都会显示正确。 我只是觉得你可能达到了 WCF 连接限制(我认为它默认为 10),然后 2 分钟后 WCF 可能会超时并允许另一个连接制作。 不,我没有达到极限。第一次失败。 【参考方案1】:如果您还没有Programming WCF Services,请先给自己弄一份。
如果客户端是WinForm或WPF,则需要使用[CallbackBehavior(UseSynchronizationContext = false)]
,否则客户端不会处理传入的消息,直到UI线程进入消息循环。
首先,WCF 中的“双工”通道并不是真正的双工!来自
的消息 客户端到服务器 可以阻止服务器正在等待来自客户端的消息 (或相反)因为消息仅在单个 WCF 通道上按顺序分派。双工 WCF 通道不会为您提供两个传入消息队列。从“TwoWay”调用返回的结果与 WCF 堆栈的这一级别的“调用”相同。一旦你了解了这一点,很多问题就会变得更清晰易懂。 p>
如果客户端是 WinForm 或 WPF,您可能需要使用[CallbackBehavior(UseSynchronizationContext = false)]
,否则客户端不会处理传入的消息,直到 UI 线程进入消息循环。
我发现了一些有助于避免死锁的规则。 (看看我的 WCF 问题,看看我的痛苦!)
服务器绝不能调用 客户端在同一连接上 来自同一个客户的电话在 处理。
和/或
客户绝不能回电 服务器在同一个连接上 用于“回调”,而 处理回调。
下一次我想我将只使用两个合同(以及因此的 TCP 连接)一个用于回调,另一个用于所有客户端->服务器请求。或者使用我自己的投票系统,因为这让我很痛苦。
对不起,我今天没有时间写一个例子。无论如何,大多数示例都适用于示例试图做的事情,但在现实生活中由于某种原因与您的应用程序有关。
我所知道的关于 WCF 示例的最佳网站是 Juval Lowy’s web site。
您可能还会发现questions I asked about WCF on Stack Overflow 很有用,因为我遇到了与您相同的问题。
另外花一两天时间阅读 Stack Overflow 上的所有 WCF 问题和答案,可以很好地了解要避免的问题。
【讨论】:
嗨伊恩,感谢所有这些提示。相关的 SO 问题的代码具有 UseSynchronizationContext = false 并且它不会在处理调用的上下文中回调 - 客户端注册订阅,然后服务器会在每个 Timer.Tick 时向每个客户端订阅者询问一个简单的问题。我也会继续查看您建议的链接。 “不是真正的双工”的正确术语是“半双工”。 非常感谢您的详细解释!! :)【参考方案2】:假设客户端是一个 WinForms 应用程序,您应该使用 Ian 的建议以及在需要时委派要在 UI 线程上完成的工作,从而使回调的处理独立于应用程序的其余部分。例如,如果服务器想要通知客户端某事,例如更改标签的文本,您可以执行以下操作:
[CallbackBehavior(UseSynchronizationContext = false)]
internal class ServiceCallback : IServiceCallback
ChangeMainFormLabel(string text)
frmMain.Instance.BeginInvoke(new Action()(() => frmMain.Instance.lblSomething.Text = text));
(Instance
是一个静态属性,它返回frmMain
的单个实例,而lblSomething
是服务器想要更改的一些Label
。)这个方法将立即返回并使服务器免于等待客户端的 UI,并且 UI 将在空闲时立即更新。最重要的是,没有死锁,因为没有人在等待任何人。
【讨论】:
这是一个控制台应用程序。我试过在工作线程上运行主循环,但它似乎没有改变任何东西。这些都符合你的想法吗? @uosɐſ:如果是控制台应用程序,您只需使用UseSynchronizationContext = false
。请记住,回调对象上的方法将在不同的线程上执行,因此同步对共享对象的任何访问。如果您发布代码示例,它可能会帮助我们查明您遇到的问题。
Here 是您建议的具有 synccontext=false 的代码示例。 (我在原帖里有,但我知道很难识别)【参考方案3】:
http://www.codeproject.com/KB/WCF/WCF_Duplex_UI_Threads.aspx
【讨论】:
【参考方案4】:抱歉,我完全忘记了示例 (:-$)。
这是我的服务器代码:ISpotifyServer.cs
[ServiceContract(CallbackContract = typeof(ISpotifyCallback))]
public interface ISpotifyService
[OperationContract(IsOneWay = true)]
void Login(string username, string password);
ISpotifyCallback.cs
[ServiceContract]
public interface ISpotifyCallback
[OperationContract(IsOneWay = true)]
void OnLoginComplete();
[OperationContract(IsOneWay = true)]
void OnLoginError();
Program.cs
class Program
static void Main(string[] args)
using (ServiceHost host = new ServiceHost(typeof(SpotifyService)))
host.Open();
Console.WriteLine("Service running.");
Console.WriteLine("Endpoints:");
foreach (ServiceEndpoint se in host.Description.Endpoints)
Console.WriteLine(se.Address.ToString());
Console.ReadLine();
host.Close();
AppData.xml
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="MetadataEnabledBehavior">
<serviceMetadata />
<serviceDebug includeExceptionDetailInFaults="True"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="MetadataEnabledBehavior" name="SpotiServer.SpotifyService">
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:9821" />
</baseAddresses>
</host>
<clear />
<endpoint address="spotiserver" binding="netTcpBinding"
name="TcpEndpoint" contract="SpotiServer.ISpotifyService"
listenUriMode="Explicit">
<identity>
<dns value="localhost"/>
<certificateReference storeName="My" storeLocation="LocalMachine"
x509FindType="FindBySubjectDistinguishedName" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" />
</service>
</services>
</system.serviceModel>
</configuration>
对于客户:Program.cs
class Program
static void Main(string[] args)
InstanceContext context = new InstanceContext(new CallbackHandler());
String username;
String password;
Console.Write("Username: ");
username = Console.ReadLine();
Console.WriteLine("Password: ");
password = ReadPassword();
SpotiService.SpotifyServiceClient client = new SpotiService.SpotifyServiceClient(context);
client.Login(username, password);
Console.ReadLine();
private static string ReadPassword()
Stack<string> passbits = new Stack<string>();
//keep reading
for (ConsoleKeyInfo cki = Console.ReadKey(true); cki.Key != ConsoleKey.Enter; cki = Console.ReadKey(true))
if (cki.Key == ConsoleKey.Backspace)
//rollback the cursor and write a space so it looks backspaced to the user
Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
Console.Write(" ");
Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
passbits.Pop();
else
Console.Write("*");
passbits.Push(cki.KeyChar.ToString());
string[] pass = passbits.ToArray();
Array.Reverse(pass);
return string.Join(string.Empty, pass);
我想这就是全部。我当然也有接口的实现,(在客户端)将结果打印到控制台,如果用户名和密码正确,则在服务器端运行“OnLoginComplete”,否则运行“OnLoginError”。让我知道它是否不起作用或者您是否需要帮助进行设置。
【讨论】:
嗨,Alxandr。感谢您花时间发布此内容,但 IsOneWay=true 是问题所在。我已经成功使用了这样的 IsOneWay=true 示例。 IsOneWay=false 是这个问题的挑战和目标。 你使用的是sockets还是http? 仅删除登录规则上的IsOneWay
不起作用?以上是关于求WCF双工“TwoWay”订阅+回调示例的主要内容,如果未能解决你的问题,请参考以下文章