У меня есть следующие контракты на обслуживание и обратный вызов (сокращенные):
Контракт на обслуживание:
[ServiceContract(CallbackContract = typeof(ISchedulerServiceCallback))]
public interface ISchedulerService
{
[OperationContract]
void Stop();
[OperationContract]
void SubscribeStatusUpdate();
}
Контракт обратного звонка:
public interface ISchedulerServiceCallback
{
[OperationContract(IsOneWay = true)]
void StatusUpdate(SchedulerStatus status);
}
Внедрение службы:
[CallbackBehavior(UseSynchronizationContext = false, ConcurrencyMode = ConcurrencyMode.Multiple)] // Tried Reentrant as well.
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] // Single due to a timer in the service that must keep time across calls.
public class SchedulerService : ISchedulerService
{
private static Action<SchedulerStatus> statusUpdate = delegate { };
public void Stop()
{
Status = SchedulerStatus.Stopped;
statusUpdate(Status);
}
private SchedulerStatus Status { get; set; }
public void SubscribeStatusUpdate()
{
ISchedulerServiceCallback sub = OperationContext.Current.GetCallbackChannel<ISchedulerServiceCallback>();
statusUpdate += sub.StatusUpdate;
}
}
Потребитель услуг:
public class SchedulerViewModel : ViewModelBase, ISchedulerServiceCallback
{
private SchedulerServiceClient proxy;
public SchedulerViewModel()
{
StopScheduler = new DelegateCommand(ExecuteStopSchedulerCommand, CanExecuteStopSchedulerCommand);
}
public void SubScribeStatusCallback()
{
ISchedulerServiceCallback call = this;
InstanceContext ctx = new InstanceContext(call);
proxy = new SchedulerServiceClient(ctx);
proxy.SubscribeStatusUpdate();
}
private SchedulerStatus _status;
private SchedulerStatus Status
{
get
{
return _status;
}
set
{
_status = value;
OnPropertyChanged();
}
}
public void StatusUpdate(SchedulerStatus newStatus)
{
Status = newStatus;
Console.WriteLine("Status: " + newStatus);
}
public DelegateCommand StopScheduler { get; private set; }
bool CanExecuteStopSchedulerCommand()
{
return true;
}
public void ExecuteStopSchedulerCommand()
{
proxy.Stop();
}
}
SchedulerViewModel
привязан к простому окну с текстовым полем и кнопкой через свойства Status
и StopScheduler
. WCF размещается в простом консольном приложении для отладки: решение настроено на запуск сначала узла службы (консольного приложения), а затем приложения WCF.
Когда я нажимаю кнопку в главном окне приложения, я ожидаю, что будет запущена команда, то есть вызов proxy.Stop();
. Это должно изменить статус статуса службы и вызвать обратный вызов. Я думаю, что это так, но время обратного вызова истекает. Отладчик зависает на строке proxy.Stop();
, и в итоге получаю сообщение об ошибке:
Эта операция запроса, отправленная на http://localhost:8089/TestService/SchedulerService/, не получила ответа в течение настроенного времени ожидания (00:00:59.9990000). Время, отведенное на эту операцию, могло быть частью более длительного тайм-аута. Это может быть связано с тем, что служба все еще обрабатывает операцию или потому, что службе не удалось отправить ответное сообщение. Рассмотрите возможность увеличения времени ожидания операции (приведя канал/прокси к IContextChannel и задав свойство OperationTimeout) и убедитесь, что служба может подключиться к клиенту.
Когда я использую SchedulerViewModel
в консольном приложении, обратный вызов работает нормально, и модель представления печатает Status: Stopped
в окне консоли. Как только я задействую другие потоки, обратный вызов больше не работает. Другие потоки представляют собой модель представления, поднимающую OnPropertyChanged
для обновления связанного текстового поля, и я не знаю, участвуют ли еще какие-либо потоки в включении/отключении команды.
Ничто в вызываемом сервисном методе не должно занимать не более миллисекунд, и я считаю, что двигаюсь в правильном направлении, полагая, что это проблема с потоками и/или зависанием пользовательского интерфейса, поскольку я видел подобные проблемы во время исследования. В большинстве были совсем другие сценарии и глубоко технические решения.
Почему это происходит, и я ничего не могу сделать, используя довольно стандартную инфраструктуру и функции WPF и WCF, чтобы включить этот обратный вызов? Моя грустная альтернатива — чтобы сервис писал статус в файл, а модель представления смотрела файл. Как это для грязного обходного пути?