为什么我的 WCF 回调超时?
Why is my WCF callback timing out?
我有以下服务和回调合同(删节):
服务合同:
[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
属性绑定到带有文本框和按钮的简单 window。 WCF由一个简单的Console app托管,用于调试:解决方案设置为先启动服务宿主(console app),再启动WCF app。
当我单击主应用程序 window 上的按钮时,我希望调用命令,即调用 proxy.Stop();
。这应该改变服务的状态并调用回调。我认为是的,但是回调超时了。调试器挂在 proxy.Stop();
行,最后我得到错误信息:
This request operation sent to
http://localhost:8089/TestService/SchedulerService/ did not receive a
reply within the configured timeout (00:00:59.9990000). 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.
当我在控制台应用程序中使用 SchedulerViewModel
时,回调工作正常,视图模型在控制台 window 中打印 Status: Stopped
。一旦我涉及其他线程,回调就不再有效。
其他线程是视图模型提升 OnPropertyChanged
以更新绑定的文本框,我不知道 enabling/disabling 命令中是否涉及更多线程。
调用的服务方法中的任何内容最多都不应超过几毫秒,我相信我正朝着正确的方向前进,相信这是一个线程 and/or UI 挂断问题,正如我所遇到的在做研究时看到了类似的问题。大多数是截然不同的场景和深入的技术解决方案。
为什么会发生这种情况,我无法使用相当标准的 WPF 和 WCF 基础结构和函数来启用此回调?我可悲的选择是让服务将状态写入文件,然后让视图模型查看文件。肮脏的解决方法如何?
不幸的是,您在 WPF 中造成了死锁。
- 当您同步调用
Stop
时,您阻塞了 UI 线程。
- 服务器处理
Stop
请求并在返回客户端之前处理所有回调。
- 来自服务器的回调是同步处理的,因此它会阻止从
Stop
返回,直到 WPF 中的回调处理程序处理 StatusUpdate
回调。但是 StatusUpdate
处理程序无法启动,因为它需要 UI 线程 - UI 线程仍在等待 Stop
的原始请求完成。
如果您使用的是 NET 4.5,解决方案很简单。您 "click" 处理程序将被标记为 async
并且您在客户端中调用 await client.StopAsync()
。
var ssc = new SchedulerServiceClient(new InstanceContext(callback));
try
{
ssc.SubscribeStatusUpdate();
await ssc.StopAsync();
}
finally
{
ssc.Close();
}
如果您使用的是 NET 4.0,则需要以其他方式异步调用 Stop
。最有可能通过 TPL。
你的控制台客户端没有这个问题,因为它只是在不同的线程上触发回调。
我创建了非常简单的解决方案,显示了 GitHub 上 WPF 和控制台应用程序之间的区别。在 WPF 客户端中,您会发现 3 个按钮 - 显示 2 种方式如何异步触发 Stop
和 1 种会导致死锁的同步调用。
此外,在我看来你根本不处理 unsubscribe - 所以一旦你的客户端断开连接,服务器将尝试调用死回调 - 这可以而且大多数也可能会导致其他客户端对 Stop
的调用超时。因此,在您的服务中 class 实施类似:
public void SubscribeStatusUpdate()
{
var sub = OperationContext.Current.GetCallbackChannel<ISchedulerServiceCallback>();
EventHandler channelClosed =null;
channelClosed=new EventHandler(delegate
{
statusUpdate -= sub.StatusUpdate;
});
OperationContext.Current.Channel.Closed += channelClosed;
OperationContext.Current.Channel.Faulted += channelClosed;
statusUpdate += sub.StatusUpdate;
}
我有以下服务和回调合同(删节):
服务合同:
[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
属性绑定到带有文本框和按钮的简单 window。 WCF由一个简单的Console app托管,用于调试:解决方案设置为先启动服务宿主(console app),再启动WCF app。
当我单击主应用程序 window 上的按钮时,我希望调用命令,即调用 proxy.Stop();
。这应该改变服务的状态并调用回调。我认为是的,但是回调超时了。调试器挂在 proxy.Stop();
行,最后我得到错误信息:
This request operation sent to http://localhost:8089/TestService/SchedulerService/ did not receive a reply within the configured timeout (00:00:59.9990000). 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.
当我在控制台应用程序中使用 SchedulerViewModel
时,回调工作正常,视图模型在控制台 window 中打印 Status: Stopped
。一旦我涉及其他线程,回调就不再有效。
其他线程是视图模型提升 OnPropertyChanged
以更新绑定的文本框,我不知道 enabling/disabling 命令中是否涉及更多线程。
调用的服务方法中的任何内容最多都不应超过几毫秒,我相信我正朝着正确的方向前进,相信这是一个线程 and/or UI 挂断问题,正如我所遇到的在做研究时看到了类似的问题。大多数是截然不同的场景和深入的技术解决方案。
为什么会发生这种情况,我无法使用相当标准的 WPF 和 WCF 基础结构和函数来启用此回调?我可悲的选择是让服务将状态写入文件,然后让视图模型查看文件。肮脏的解决方法如何?
不幸的是,您在 WPF 中造成了死锁。
- 当您同步调用
Stop
时,您阻塞了 UI 线程。 - 服务器处理
Stop
请求并在返回客户端之前处理所有回调。 - 来自服务器的回调是同步处理的,因此它会阻止从
Stop
返回,直到 WPF 中的回调处理程序处理StatusUpdate
回调。但是StatusUpdate
处理程序无法启动,因为它需要 UI 线程 - UI 线程仍在等待Stop
的原始请求完成。
如果您使用的是 NET 4.5,解决方案很简单。您 "click" 处理程序将被标记为 async
并且您在客户端中调用 await client.StopAsync()
。
var ssc = new SchedulerServiceClient(new InstanceContext(callback));
try
{
ssc.SubscribeStatusUpdate();
await ssc.StopAsync();
}
finally
{
ssc.Close();
}
如果您使用的是 NET 4.0,则需要以其他方式异步调用 Stop
。最有可能通过 TPL。
你的控制台客户端没有这个问题,因为它只是在不同的线程上触发回调。
我创建了非常简单的解决方案,显示了 GitHub 上 WPF 和控制台应用程序之间的区别。在 WPF 客户端中,您会发现 3 个按钮 - 显示 2 种方式如何异步触发 Stop
和 1 种会导致死锁的同步调用。
此外,在我看来你根本不处理 unsubscribe - 所以一旦你的客户端断开连接,服务器将尝试调用死回调 - 这可以而且大多数也可能会导致其他客户端对 Stop
的调用超时。因此,在您的服务中 class 实施类似:
public void SubscribeStatusUpdate()
{
var sub = OperationContext.Current.GetCallbackChannel<ISchedulerServiceCallback>();
EventHandler channelClosed =null;
channelClosed=new EventHandler(delegate
{
statusUpdate -= sub.StatusUpdate;
});
OperationContext.Current.Channel.Closed += channelClosed;
OperationContext.Current.Channel.Faulted += channelClosed;
statusUpdate += sub.StatusUpdate;
}