Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[18.223.106.199] |
|
Сообщ.
#1
,
|
|
|
Нашел вот такую забавную штуку есть простенький сервис WCF, который по таймеру отдает клиентам сообщение. Также клиент может сам полезть за сообщением на сервер.
Сервер: Контракт сервиса [ServiceContract(CallbackContract = typeof(IClientCallback))] public interface ISampleService { [OperationContract(IsOneWay = true)] void RegisterClient(); [OperationContract(IsOneWay = true)] void UnregisterClient(); [OperationContract] String RecieveString(); } Контракт коллбека [ServiceContract] public interface IClientCallback { [OperationContract(IsOneWay = true)] void NewMessage(String message); } Реализация сервиса [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] public class SampleService : ISampleService { private List<IClientCallback> _Callbacks = new List<IClientCallback>(); private object lockCallbacks = new object(); private Timer _TimerCallbacks; public SampleService() { _TimerCallbacks = new Timer(OnTimer, null, 0, 700); } // timer callback public void OnTimer(object state) { lock (lockCallbacks) { for (int i = 0; i < _Callbacks.Count; i++) try { _Callbacks[i].NewMessage(String.Format("Client callback", DateTime.Now)); } catch (CommunicationException) { // channel faulted _Callbacks.RemoveAt(i); i--; } } } #region ISampleService Members public void RegisterClient() { OperationContext context = OperationContext.Current; IClientCallback c = context.GetCallbackChannel<IClientCallback>(); lock (lockCallbacks) _Callbacks.Add(c); } public void UnregisterClient() { OperationContext context = OperationContext.Current; IClientCallback c = context.GetCallbackChannel<IClientCallback>(); lock (lockCallbacks) _Callbacks.Remove(c); } public string RecieveString() { return "Client request"; } #endregion } Теперь само создание сервиса SampleService instance = new SampleService(); ServiceHost host = new ServiceHost(instance); NetTcpBinding binding = new NetTcpBinding(SecurityMode.None); binding.MaxReceivedMessageSize = Int32.MaxValue; host.AddServiceEndpoint(typeof(ISampleService), binding, new Uri("net.tcp://localhost:888/SampleService")); // set all throttlings to maximum to ensure the problem is not here ServiceThrottlingBehavior bhvrThrottling = new ServiceThrottlingBehavior(); bhvrThrottling.MaxConcurrentCalls = Int32.MaxValue; bhvrThrottling.MaxConcurrentInstances = Int32.MaxValue; bhvrThrottling.MaxConcurrentSessions = Int32.MaxValue; host.Description.Behaviors.Add(bhvrThrottling); // mex host.Description.Behaviors.Add(new ServiceMetadataBehavior()); Binding mexBinding = MetadataExchangeBindings.CreateMexTcpBinding(); host.AddServiceEndpoint(typeof(IMetadataExchange), mexBinding, new Uri("net.tcp://localhost:888/SampleService/mex")); host.Open(); Клиент: Для клиента сгенерен прокси класс, а также написана реализация коллбека class ServiceCallback : ISampleServiceCallback { #region ISampleServiceCallback Members public void NewMessage(string message) { Debug.WriteLine(message); } #endregion } А теперь начинается самое интересное. Есть следующий код, который подписывается на сообщения от сервера и в свою очередь сам запрашивает сообщение каждые 700мс ServiceCallback service = new ServiceCallback(); SampleServiceClient client = new SampleServiceClient(new InstanceContext(service)); client.RegisterClient(); Debug.WriteLine("Connected..."); for (;;) { Debug.WriteLine(client.RecieveString()); Thread.Sleep(700); } 1) Если вставляю это в консольное приложение - работает как часы. 2) WinForm и WPF в событии Load (Loaded) - рушатся с эксепшеном TimeoutException на client.RecieveString() 3) WinForm и WPF в Main перед Application.EnableVisualStyles(); - работает как нужно Где туплю? Полный пример см в аттаче. PS Переименовать с WCF1.jpg в WCF1.rar PPS используется класс Debug Прикреплённый файлWCF1.jpg (162.37 Кбайт, скачиваний: 214) |
Сообщ.
#2
,
|
|
|
Еесли запустить это в отдельном потоке, то все работает. Предполагаю, что это как-то связано с однопоточностью интерфейса. Только как?? Так не должно быть
ThreadPool.QueueUserWorkItem((s) => { ServiceCallback service = new ServiceCallback(); SampleServiceClient client = new SampleServiceClient(new InstanceContext(service)); client.RegisterClient(); Debug.WriteLine("Connected..."); for (; ; ) { Debug.WriteLine(client.RecieveString()); Thread.Sleep(700); } }); |
Сообщ.
#3
,
|
|
|
Ответ нашел здесь, codehelper.ru :
По умолчанию в WCF callbacks настроены так, чтобы вылеть в том же потоке, в котором создан сам callback-объект. Если callback-объект создается в потоке, связанном с GUI (например в форме WinForms), то он становится привязанным к этому потоку. Это создает ситуацию аналогичную deadlock`у: callback ждет когда закончит выполнение инициировавший его метод, а метод не может закончиться, потому что ждет когда выполнится callback. В итоге метод вызова сервиса прекращает работу с сообщением о таймауте. Исправляется это следующим образом: Изменяем ConcurrencyMode в поведении сервиса на Reentrant: [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode=ConcurrencyMode.Reentrant)] public class SampleService : ISampleService { // ... } Устанавливаем значение свойства UseSynchronizationContext в false для поведения callback-объекта: [CallbackBehavior(UseSynchronizationContext = false)] class ServiceCallback : ISampleServiceCallback { // ... } |
Сообщ.
#4
,
|
|
|
GarF1eld Спасибо, огромное.
Случилось аналогичная ситуация, не знал что делать. Отлично помог. |