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();        
  }
}

タイマーなしのサブスクライブメソッドで別のスレッドを使用すると、次のように機能します。

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

サブスクライブメソッドの最後にThread.Sleep(3000)を配置すると、タイマー(3秒間)でも機能するため、サブスクライブメソッドの終了後にコールバックオブジェクトへのチャネルが閉じられる可能性があります。OperationContext.Current.GetCallbackChannel();で取得されたコールバックオブジェクトのクラススコープ変数を使用します。method-scope変数の代わりに役に立ちませんでした。

以前、タイマーサービスのタイマーの経過イベントハンドラーに新しいスレッドを作成して、高速化しようとしました。ObjectDisposedExceptionがスローされ、 「破棄されたオブジェクトにアクセスできません。オブジェクト名:'System.ServiceModel.Channels.ServiceChannel」というメッセージが表示されます。次に、サービスを単純​​化しようとしましたが、タイマーのみを使用しても説明どおりに問題が発生することがわかりましたが、例外は、クライアントのコールバックオブジェクトへの接続がどこかで失われたことを示していると思います。タイマースレッドで新しいスレッドを作成しないと、例外がないのは不思議です。コールバックメソッドは呼び出されません。

4

1 に答える 1

1

デュプレックスバインディングでは、2つのチャネルの寿命がリンクされます。TimerServiceへのチャネルが閉じると、CallbackTargetへのコールバックチャネルも閉じます。閉じられたチャネルを使用しようとすると、ObjectDisposedExcpetionを取得できます。あなたの場合、これは悪いことです。なぜなら、OnTimeElasped()呼び出しを受信するためだけにSubscribe()チャネルを開いたままにしたくないからです...そして、私はあなたが無限に長い間サブスクライブしたいと思っていると思います。

デュプレックスチャネルはあなたの生活を楽にしようとしていますが、あなたのニーズに合いません。舞台裏では、デュプレックスチャネルが実際にCallbackTargetの2番目のWCFサービスホストを作成しています。コールバックを受信するためにクライアントのサービスホストを手動で作成する場合は、Subscribe()チャネルとは関係なくその存続期間を管理できます。

以下は、アイデアを示す完全に機能するコマンドラインプログラムです。

  1. TimerServiceを作成する
  2. 通知を受け取るTimerClientを作成します
  3. サブスクライブ呼び出しの一部として、TimerClientのエンドポイントアドレスをTimerServiceに渡します
  4. TimerServiceは、Subscribe()から取得したアドレスを使用して、TimerClientに通知を送信します。

1回の呼び出しに必要な時間より長く開いたままのチャネルはないことに注意してください。

標準の免責事項:これは、「デュプレックスのような」動作を作成する方法を示すことを目的としています。エラー処理やその他のショートカットが不足しています。

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 に答える