WCF回調中的死鎖


以前我在文章《WCF入門(六)——回調》中介紹了在WCF中通過回調的方式實現雙工通信,然而在回調的時候是非常容易出現死鎖的,本文就簡單的介紹幾種常見的死鎖的方式和解決方案。

一、服務器端死鎖

對於如下服務:

    [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回調又因為Download函數又等待着Download函數返回釋放UI線程才能執行,這樣又形成了一個死鎖。而控制台程序沒有UI線程,不會出現這種死鎖。

對於這種死鎖,根本的方案就是修改回調回調函數的通知線程,將其改為在非UI線程中執行。WCF中可以通過在客戶端回調函數類中的CallbackBehaviorAttribute中控制這一行為

    [CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant, UseSynchronizationContext = false)]
    class CallBack : DownloadServiceCallback

我在這里還設置了CallbackBehaviorAttribute的ConcurrencyMode參數,這個參數在本例中是不必要的,但我還是習慣性的將其帶上了,至於它在什么時候會用到,請讀者朋友們自行分析。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM