为什么我的WCF回调超时?

时间:2015-03-03 16:57:09

标签: .net wpf wcf events wcf-callbacks

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

服务合同:

[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通过其StatusStopScheduler属性绑定到带有文本框和按钮的简单窗口。 WCF由一个简单的Console应用程序托管进行调试:解决方案设置为首先启动服务主机(控制台应用程序),然后启动WCF应用程序。

当我点击主应用程序窗口上的按钮时,我希望调用该命令,即调用proxy.Stop();。这应该改变服务状态的状态并调用回调。我认为确实如此,但回调超时。调试器挂起在proxy.Stop();行,最终我收到错误消息:

  

此请求操作发送到   http://localhost:8089/TestService/SchedulerService/没有收到   在配置的超时(00:00:59.9990000)内回复。时间   分配给这个操作可能是一个更长的一部分   超时。这可能是因为该服务仍在处理中   操作或因为服务无法发送回复消息。   请考虑增加操作超时(通过强制转换   通道/代理到IContextChannel并设置OperationTimeout   property)并确保该服务能够连接到   客户端。

当我在Console应用程序中使用SchedulerViewModel时,回调正常,并且viewmodel在控制台窗口中打印Status: Stopped。一旦我涉及其他线程,回调就不再起作用了。 其他线程是viewmodel引发OnPropertyChanged以更新绑定文本框,我不知道是否还有更多线程参与启用/禁用命令。

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

为什么会发生这种情况,使用相当标准的WPF和WCF基础架构和功能,我无法做任何事情来启用此回调?我悲伤的替代方案是服务将状态写入文件,以及查看文件以查看文件。对于一个肮脏的解决方法,这是怎么回事?

1 个答案:

答案 0 :(得分:0)

不幸的是,你在WPF中创建了一个死锁。

  1. 当您同步拨打Stop时,会阻止您的UI线程。
  2. 服务器处理Stop请求,并在返回客户端之前处理所有回调。
  3. 同步处理来自服务器的回调,因此阻止从Stop返回,直到WPF中的回调处理程序处理StatusUpdate回调。但StatusUpdate处理程序无法启动,因为它需要UI线程 - 并且UI线程仍在等待Stop完成的原始请求。
  4. 如果您使用的是.NET 4.5,解决方案很简单。你&#34;点击&#34;处理程序将标记为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。

    您在控制台客户端中没有遇到此问题,因为它只是在不同的线程上触发回调。

    我创建了一个非常简单的解决方案,展示了WPF与&之间的区别。 GitHub上的控制台应用。在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;
    }
    
相关问题