1

我有以下服务和回调合同(删节):

服务合同:

[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以更新绑定的文本框,我不知道是否有更多线程参与启用/禁用命令。

调用的服务方法中的任何内容最多都不应超过毫秒,我相信我正朝着正确的方向前进,相信这是一个线程和/或 UI 挂起问题,因为我在进行研究时看到了类似的问题。大多数是完全不同的场景和深入的技术解决方案。

为什么会发生这种情况,我是否无能为力,使用相当标准的 WPF 和 WCF 基础结构和函数来启用此回调?我可悲的选择是让服务将状态写入文件,并让视图模型观察文件。这是一个肮脏的解决方法?

4

1 回答 1

0

不幸的是,您正在 WPF 中创建死锁。

  1. Stop同步调用时会阻塞 UI 线程。
  2. 服务器处理Stop请求并在返回给客户端之前处理所有回调。
  3. 来自服务器的回调是同步处理的,因此它会阻止返回,Stop直到 WPF 中的回调处理程序处理StatusUpdate回调。但StatusUpdate处理程序无法启动,因为它需要 UI 线程 - 并且 UI 线程仍在等待原始请求Stop完成。

如果您使用的是 NET 4.5,则解决方案很简单。您的“点击”处理程序将被标记为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 和 Console App 之间的区别。在 WPF 客户端中,您会发现 3 个按钮 - 显示 2 种Stop异步触发方式和 1 个同步调用,这将导致死锁。

此外,在我看来,您根本不处理取消订阅-因此,一旦您的客户端断开连接,服务器将尝试调用死回调-这可能而且很可能还会导致Stop来自其他客户端的调用超时。因此,在您的服务类中实现类似:

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;
}
于 2015-03-10T01:28:04.617 回答