WCFでデッドロックまたはタイムアウトが発生

5549 ワード

WCF电脑里的Dedlock


 
一、サーバー側デッドロック
次のサービスの場合:
   
 [ServiceContract(CallbackContract = typeof(INotify))]

    public class DownloadService

    {

        [OperationContract]

        public void Download()

        {

            // 

            //.....



            // 

            var callback = OperationContext.Current.GetCallbackChannel<INotify>();

            callback.DownloadComplete();

        }

    }



    interface Inotify

    {

        [OperationContract]

        void DownloadComplete();

    }

 
まず、クライアントとしてコンソールプログラムを使用します.コードは次のとおりです.
    
class Program

    {

        static void Main(string[] args)

        {

            var context = new System.ServiceModel.InstanceContext(new CallBack());

            var client = new DownloadServiceClient(context);

            client.Download();

        }

    }



    class CallBack : DownloadServiceCallback

    {

        public void DownloadComplete()

        {

            Console.WriteLine("finished");

        }

    }

 
このプログラムを実行すると、次のような異常が発生します.
未処理の異常:System.ServiceModel.FaultException`1[System.ServiceModel.ExceptionDetail]:現在のメール処理が完了するまで返信が来ないため、この操作はデッドロックになります.無秩序なメール処理を許可する場合は、ServiceBehaviorAttributeで再入力可能なまたは複数のConcurrencyModeを指定します.
この異常は、現在のサービスではコンカレントがサポートされておらず、Download関数を処理するときにチャネルがシリアルであり、メッセージを返すときはDownload関数の処理メッセージを返してから、DownloadCompleteコールバックを返してコールバックの結果を取得しなければならないことが明らかになった.しかし、Download関数自体はDownloadComplete関数の戻りを待っており、デッドロックが形成されています.
この例外は、サービスのServiceBehaviorのConcurrencyModeをReentrantまたはMultipleに変更すればよいという修正案も示しています.
    [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)]    public class DownloadService
実際、この問題にはもう一つの解決策があります.コールバックの完了を待つ必要がなければ、コールバック関数をOneWayに設定することができます.これによりDownload関数はコールバック関数の戻り結果を待たず,互いに待つことでデッドロックを招くことはない.
  
  interface Inotify

    {

        [OperationContract(IsOneWay=true)]

        void DownloadComplete();

    }

 
二、クライアントのデッドロック
システム自身がサーバ側のデッドロックを分析できるため、サーバ側のデッドロックは発見しやすく処理しやすい.しかし、クライアントのデッドロックは容易に発見されません.
前の処理後、サーバ側のデッドロック問題は解決され、クライアントは順調に処理できます.今回はクライアントコードをWinFormバージョンのプログラムに入れ,UIスレッドで処理する.
   
 private void button1_Click(object sender, EventArgs e)

    {

        var context = new System.ServiceModel.InstanceContext(new CallBack());

        var client = new DownloadServiceClient(context);

        client.Download();

    }

 
上記のコードを実行すると、ボタンをクリックすると、ウィンドウが応答しないことがわかります.WCFのコールバック関数のデフォルトはUIスレッドで実行されるため、Download関数がDownloadCompleteコールバックの実行を待ってから戻るのを待つDownloadCompleteコールバックが発生します.DownloadCompleteコールバックはDownload関数がDownload関数が戻ってUIスレッドを解放するのを待っているため、デッドロックが発生します.コンソールプログラムにはUIスレッドがなく、このようなデッドロックは発生しません.
このようなデッドロックの場合、根本的な方法は、コールバックコール関数の通知スレッドを変更し、非UIスレッドで実行することである.WCFでは、クライアントコールバック関数クラスのCallbackBehaviorAttributeでこの動作を制御できます.
   
 [CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant, UseSynchronizationContext = false)]

    class CallBack : DownloadServiceCallback

 
ここではCallbackBehaviorAttributeのConcurrencyModeパラメータも設定しましたが、このパラメータはこの例では必要ありませんが、習慣的に持ってきました.いつ使うかは、読者の皆さんにご分析ください.