次のサービスとコールバック コントラクト (要約) があります。
サービス契約:
[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
を介して、テキスト ボックスとボタンを備えた単純なウィンドウにバインドされます。WCF は、デバッグ用の単純なコンソール アプリによってホストされます。ソリューションは、最初にサービス ホスト (コンソール アプリ) を開始し、次に WCF アプリを開始するように設定されています。Status
StopScheduler
アプリのメイン ウィンドウのボタンをクリックすると、コマンドが呼び出される、つまりproxy.Stop();
. これにより、サービスのステータスが変更され、コールバックが呼び出されます。だと思いますが、コールバックがタイムアウトします。デバッガーが行proxy.Stop();
でハングし、最終的に次のエラー メッセージが表示されます。
http://localhost:8089/TestService/SchedulerService/に送信されたこの要求操作は 、構成されたタイムアウト (00:00:59.9990000) 内に応答を受信しませんでした。この操作に割り当てられた時間は、より長いタイムアウトの一部であった可能性があります。これは、サービスがまだ操作を処理中であるか、サービスが応答メッセージを送信できなかったことが原因である可能性があります。(チャネル/プロキシを IContextChannel にキャストし、OperationTimeout プロパティを設定することによって) 操作のタイムアウトを増やすことを検討し、サービスがクライアントに接続できることを確認してください。
コンソール アプリでを使用するSchedulerViewModel
と、コールバックが正常に機能し、viewmodelStatus: Stopped
がコンソール ウィンドウに出力されます。他のスレッドを巻き込むとすぐに、コールバックは機能しなくなります。バインドされたテキストボックスを更新するために発生するビューモデルである他のスレッドでありOnPropertyChanged
、コマンドの有効化/無効化にさらにスレッドが関与しているかどうかはわかりません。
呼び出されたサービス メソッドでは、最大でミリ秒以上かかることはありません。調査中に同様の問題が発生したため、これはスレッドや UI ハングアップの問題であると信じて正しい方向に向かっていると思います。そのほとんどはまったく異なるシナリオであり、高度な技術的ソリューションでした。
このコールバックを有効にするために、かなり標準的な WPF および WCF インフラストラクチャと関数を使用してできることはありませんか? 私の悲しい代替案は、サービスがステータスをファイルに書き込み、ビュー モデルがファイルを監視することです。汚い回避策はどうですか?