0

我想制作一个 WCF 计时器服务,客户端可以在其中注册,以便在经过一定时间后从服务中回调。问题是客户端没有被回调。不抛出异常。

回调接口为:

[ServiceContract]
public interface ITimerCallbackTarget
{
  [OperationContract(IsOneWay = true)]
  void OnTimeElapsed(int someInfo);
}

该服务如下所示:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
  ConcurrencyMode = ConcurrencyMode.Single)]  
public class TimerService : ITimerService
   private readonly Timer _timer = new Timer(2000); //System.Timers.Timer

   public void Subscribe()
   {
     ITimerCallbackTarget listener = 
       OperationContext.Current.GetCallbackChannel<ITimerCallbackTarget>();

     _timer.Elapsed += (p1, p2) =>
     {
       listener.OnTimeElapsed(999);
      };
     _timer.Start();
   }

客户端使用的回调方法是:

private class TimerCallbackTarget : ITimerCallbackTarget
{
  public void OnTimeElapsed(int someInfo)
  {
    Console.WriteLine(someInfo);
  }
}

客户端注册如下:

private static void TestTimerService()
{
  InstanceContext callbackInstance = new InstanceContext(new TimerCallbackTarget());

  using (DuplexChannelFactory<ITimerService> dcf =
    new DuplexChannelFactory<ITimerService>(callbackInstance,  
      "TimerService_SecureTcpEndpoint"))
  {
    ITimerService timerProxy = dcf.CreateChannel();

    timerProxy.Subscribe();        
  }
}

如果我在没有Timer的 subscribe 方法中使用不同的线程,它可以工作:

  ThreadPool.QueueUserWorkItem(p =>
  {
    listener.OnTimeElapsed(999);
  });

如果我将Thread.Sleep(3000)放在 subscribe 方法的末尾,它甚至可以与Timer一起使用(三秒钟) ,所以我的猜测是,回调对象的通道可能在 subscribe 方法完成后关闭。对通过OperationContext.Current.GetCallbackChannel()检索到的回调对象使用类范围变量;而不是方法范围变量没有帮助。

以前我尝试在计时器服务的计时器的经过事件处理程序中创建新线程以使其更快。引发ObjectDisposedException并显示以下消息:“无法访问已处置的对象。对象名称:'System.ServiceModel.Channels.ServiceChannel”。然后我尝试简化我的服务,发现即使只使用Timer也会导致所描述的问题,但我猜这个异常表明与客户端回调对象的连接在某个地方丢失了。奇怪的是,如果我不在Timer线程中创建新线程,没有异常。只是没有调用回调方法。

4

1 回答 1

1

在双工绑定中,两个通道的生命周期是相关联的。如果 TimerService 的通道关闭,则 CallbackTarget 的回调通道也会关闭。如果您尝试使用已关闭的通道,您会得到一个 ObjectDisposedExcpetion。在您的情况下,这很糟糕,因为您不想让 Subscribe() 频道保持打开状态只是为了接收 OnTimeElasped() 调用......而且我假设您想订阅无限长的时间。

双工频道试图让您的生活更轻松,但不符合您的需求。在幕后,双工通道实际上是为 CallbackTarget 创建第二个 WCF 服务主机。如果您手动创建客户端的服务主机以接收回调,那么您可以独立于 Subscribe() 通道管理其生命周期。

下面是一个功能齐全的命令行程序,演示了这个想法:

  1. 创建一个 TimerService
  2. 创建一个 TimerClient 来接收通知
  3. 作为订阅调用的一部分,将 TimerClient 的端点地址传递给 TimerService
  4. TimerService 使用它从 Subscribe() 获得的地址向 TimerClient 发送通知。

请注意,任何通道的打开时间都不会超过发出单个呼叫所需的时间。

标准免责声明:这旨在展示如何创建“类似双工”的行为。缺乏错误处理和其他捷径。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Timers;
using System.ServiceModel.Description;

namespace WcfConsoleApplication
{
    [ServiceContract]
    public interface ITimerCallbackTarget
    {
        [OperationContract(IsOneWay = true)]
        void OnTimeElapsed(int someInfo);
    } 

    [ServiceContract]
    public interface ITimerService
    {
        [OperationContract(IsOneWay = true)]
        void Subscribe(string address);
    }


    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
                     ConcurrencyMode = ConcurrencyMode.Single)]
    public class TimerService : ITimerService
    {
        private readonly Timer _timer = new Timer(2000);
        private ChannelFactory<ITimerCallbackTarget> _channelFac;
        private int _dataToSend = 99;

        public void Subscribe(string address)
        {
            // note: You can also load a configured endpoint by name from app.config here,
            //       and still change the address at runtime in code.
            _channelFac = new ChannelFactory<ITimerCallbackTarget>(new BasicHttpBinding(), address);

            _timer.Elapsed += (p1, p2) =>
            {
                ITimerCallbackTarget callback = _channelFac.CreateChannel();
                callback.OnTimeElapsed(_dataToSend++);

                ((ICommunicationObject)callback).Close();

                // By not keeping the channel open any longer than needed to make a single call
                // there's no risk of timeouts, disposed objects, etc.
                // Caching the channel factory is not required, but gives a measurable performance gain.
            };
            _timer.Start();
        }
    }

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
                     ConcurrencyMode = ConcurrencyMode.Single)]
    public class TimerClient : ITimerCallbackTarget
    {
        public void OnTimeElapsed(int someInfo)
        {
            Console.WriteLine("Got Info: " + someInfo);
        }
    }


    class Program
    {
        static void Main(string[] args)
        {

            ServiceHost hostTimerService = new ServiceHost(typeof(TimerService), new Uri("http://localhost:8080/TimerService"));
            ServiceHost hostTimerClient = new ServiceHost(typeof(TimerClient), new Uri("http://localhost:8080/TimerClient"));
            ChannelFactory<ITimerService> proxyFactory = null;

            try
            {
                // start the services
                hostTimerService.Open();
                hostTimerClient.Open();

                // subscribe to ITimerService
                proxyFactory = new ChannelFactory<ITimerService>(new BasicHttpBinding(), "http://localhost:8080/TimerService");
                ITimerService timerService = proxyFactory.CreateChannel();
                timerService.Subscribe("http://localhost:8080/TimerClient");
                ((ICommunicationObject)timerService).Close();

                // wait for call backs...
                Console.WriteLine("Wait for Elapsed updates. Press enter to exit.");
                Console.ReadLine();
            }
            finally
            {
                hostTimerService.Close();
                hostTimerClient.Close();
                proxyFactory.Close();
            }
        }
    }
}
于 2012-06-21T05:42:41.633 回答