我使用WCF和WSDualHttpBinding实现了我自己的发布/订阅模式实现代码,但是我稍后会解释超时问题,现在让我展示一下我在做什么:
public interface IEventSubscriber
{
[OperationContract]
void NotifyEvent(EventNotification notification);
[OperationContract]
void NotifyServiceDisconnecting();
}
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IEventSubscriber))]
public interface IEventPublisherService
{
[OperationContract(IsOneWay = false, IsInitiating = true)]
void Subscribe(string culture);
[OperationContract(IsOneWay = false, IsInitiating = false, IsTerminating = true)]
void Unsubscribe();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
internal class EventPublisherServiceImpl : IEventPublisherService
{
ServiceHost host;
public bool StartService()
{
bool ret = false;
try
{
Uri baseAddress = new Uri(ConfigurationManager.AppSettings[GlobalConstants.CfgKeyConfigEventPublishserServiceBaseAddress].ToString());
EventHelper.AddEvent(string.Format("Event Publisher Service on: {0}", baseAddress.ToString()));
host = new ServiceHost(this, baseAddress);
// duplex session enable http binding
WSDualHttpBinding httpBinding = new WSDualHttpBinding(WSDualHttpSecurityMode.None);
httpBinding.ReceiveTimeout = TimeSpan.FromMinutes(10);
httpBinding.ReliableSession = new ReliableSession();
httpBinding.ReliableSession.Ordered = true;
httpBinding.ReliableSession.InactivityTimeout = TimeSpan.FromMinutes(10);
host.AddServiceEndpoint(typeof(IEventPublisherService), httpBinding, baseAddress);
// Enable metadata publishing.
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
host.Description.Behaviors.Add(smb);
// Open the ServiceHost to start listening for messages.
host.Open();
ret = true;
}
catch (Exception e)
{
EventHelper.AddException(e.Message);
}
return ret;
}
...
}
现在在我的实现类中,我有一个存储在内存中的订阅者列表,当新通知到达时,为每个订阅者执行以下代码:
...
/// <summary>
/// List of active subscribers
/// </summary>
private static Dictionary<IEventSubscriber, string> subscribers = new Dictionary<IEventSubscriber, string>();
...
我这样使用它:
internal void Subscribe(string culture)
{
lock (subscribers)
{
// Get callback contract as specified on the service definition
IEventSubscriber callback = OperationContext.Current.GetCallbackChannel<IEventSubscriber>();
// Add subscriber to the list of active subscribers
if (!subscribers.ContainsKey(callback))
{
subscribers.Add(callback, culture);
}
}
}
...
private void OnNotificationEvent(NormalEvent notification)
{
lock (subscribers)
{
List<IEventSubscriber> listToRemove = new List<IEventSubscriber>();
// Method signature: Parallel.ForEach(IEnumerable<TSource> source, Action<TSource> body)
Parallel.ForEach(subscribers, kvp =>
{
try
{
kvp.Key.NotifyEvent(new EventNotification(notification, kvp.Value));
}
catch (Exception ex)
{
EventHelper.AddException(string.Format("Error notifying event notification to client: {0} - removing this one", ex.Message));
listToRemove.Add(kvp.Key);
}
} //close lambda expression
); //close method invocation
Parallel.ForEach(listToRemove, subs =>
{
try
{
subs.NotifyServiceDisconnecting();
}
catch (Exception ex) {
EventHelper.AddException(string.Format("Failed to notify client that is to be removed: {0}",
ex.Message));
}
subscribers.Remove(subs);
}
);
}
}
这有什么问题,当达到超时时(请注意我为ReceiveTimeout和非活动超时设置了10分钟)列表中的订阅者进入故障状态,并在中捕获以下异常OnNotificationEvent
* 无法完成“NotifyEvent”操作,因为会话通道超时等待接收消息。要增加超时,请在配置文件中的绑定上设置receiveTimeout属性,或者直接在Binding上设置ReceiveTimeout属性。 *
好的,我可以增加超时值,但如果我这样做,它最终会发生一段时间。
我的问题是:我在尝试实现这种模式时做错了什么?或存在任何其他方式来实现这种模式避免这个问题的更好方法?或存在任何重新连接故障回调通道的方式(因为我正在阅读它是不可能的,但由于我无法通知客户端连接丢失,让客户盲目不知道通信结束了!?或者是一种表达他与出版商失去联系的知识的方式!?)
当然解决方案就像ping消息已经过时了:)但是好吧,如果没有更好的东西看起来像我要实现类似的东西......
谢谢
答案 0 :(得分:1)
目前解决方案是将超时更改为无限值:
// duplex session enable http binding
WSDualHttpBinding httpBinding = new WSDualHttpBinding(WSDualHttpSecurityMode.None);
httpBinding.ReceiveTimeout = TimeSpan.MaxValue;
httpBinding.ReliableSession = new ReliableSession();
httpBinding.ReliableSession.Ordered = true;
httpBinding.ReliableSession.InactivityTimeout = TimeSpan.MaxValue;
答案 1 :(得分:0)
你正在使用Parallel.ForEach,但我不确定这是否足够。 AFAIR Parallel.ForEach不会在单独的线程中执行每次迭代。
我想建议在OnNotificationEvent中为每个订阅者启动单独的线程,并使用lock来确保foreach不会被Collection修改的异常中断:
lock (_subscribersSync)
foreach (var chatter in subscribers)
{
Logger.Log.DebugFormat("putting all users to {0}", subscribers.Key.Name);
Thread th = new Thread(PublishAllUserMessage);
th.Start(new MessageData() { Message = "", Subscriber = chatter.Key};
}
void PublishAllUserMessage(object messageData)
{
MessageData md = (MessageData)messageData;
try
{
md.Subscriber.NotifyEvent(...event parameters here ...);
}
catch (Exception ex)
{
Logger.Log.Error(string.Format("failed to publish message to '{0}'", md.Subscriber.Name), ex);
KickOff(md.Subscriber);
}
}
object _subscribersSync = new object();
void KickOff(IEventSubscriber p)
{
lock (_subscribersSync)
{
subscribers.Remove(p);
Logger.Log.WarnFormat("'{0}' kicked off", p.Name);
}
}
public class MessageData
{
public string Message;
public IEventSubscriber Subscriber;
}