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を介して、テキスト ボックスとボタンを備えた単純なウィンドウにバインドされます。WCF は、デバッグ用の単純なコンソール アプリによってホストされます。ソリューションは、最初にサービス ホスト (コンソール アプリ) を開始し、次に WCF アプリを開始するように設定されています。StatusStopScheduler

アプリのメイン ウィンドウのボタンをクリックすると、コマンドが呼び出される、つまりproxy.Stop();. これにより、サービスのステータスが変更され、コールバックが呼び出されます。だと思いますが、コールバックがタイムアウトします。デバッガーが行proxy.Stop();でハングし、最終的に次のエラー メッセージが表示されます。

http://localhost:8089/TestService/SchedulerService/に送信されたこの要求操作は 、構成されたタイムアウト (00:00:59.9990000) 内に応答を受信しませんでした。この操作に割り当てられた時間は、より長いタイムアウトの一部であった可能性があります。これは、サービスがまだ操作を処理中であるか、サービスが応答メッセージを送信できなかったことが原因である可能性があります。(チャネル/プロキシを IContextChannel にキャストし、OperationTimeout プロパティを設定することによって) 操作のタイムアウトを増やすことを検討し、サービスがクライアントに接続できることを確認してください。

コンソール アプリでを使用するSchedulerViewModelと、コールバックが正常に機能し、viewmodelStatus: 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 とコンソール アプリの違いを示す非常に単純なソリューションを作成しました。WPF クライアントには 3 つのボタンがあります -Stop非同期的に起動する 2 つの方法と、デッドロックを引き起こす 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 に答える