2009-09-11 70 views
1

我有一個wcf客戶端,在與wcf服務進行一些複雜的交互之前,請執行一項簡單的服務檢查以確保活着。由於連接失敗的超時時間爲一分鐘,我想異步實現此檢查。如何在與其他線程上的服務器通信時保持我的WPF UI響應?

這是我有:

//Method on the main UI thread 
public void DoSomeComplicatedInteraction() 
{ 
    If(ConnectionIsActive()) DoTheComplicatedInteraction(); 
} 


private bool ConnectionIsActive() 
    { 
     //Status is a Textblock on the wpf form 
     Status.Text = "Checking Connection"; 
     var ar = BeginConnectionIsActive(); 
     while (!ar.IsCompleted) 
     { 
      Status.Text += "."; 
      Thread.Sleep(100); 
     } 
     EndConnectionIsActive(ar); 
     //IsConnected is a boolean property 
     return IsConnected; 
    } 

    private IAsyncResult BeginConnectionIsActive() 
    { 
     //checkConnection is Func<bool> with value CheckConnection 
     return checkConnection.BeginInvoke(null,checkConnection); 
    } 


    private void EndConnectionIsActive(IAsyncResult ar) 
    { 
     var result=((Func<bool>)ar.AsyncState).EndInvoke(ar); 
     IsConnected = result; 
    } 

    private bool CheckConnection() 
    { 
     bool succes; 
     try 
     { 
      //synchronous operation 
      succes=client.Send(RequestToServer.GetPing()).Succes; 
     } 
     catch 
     { 
      succes = false; 
     } 
     return succes; 
    } 

這工作。只有當我嘗試通過在服務器的Send方法中添加Thread.Sleep來模擬緩慢的服務器響應時,UI變得無響應。此外,狀態文本塊的文本沒有明顯更新。 看來我需要某種Application.DoEvents方法。 還是我需要一種不同的方法?編輯: 確實,不同的方法是必要的。使用Thread.Sleep將阻止UI,當我們在主UI線程上調用時。 這是我如何解決它:

//Method on the main UI thread 
public void DoSomeComplicatedInteraction() 
{ 
    IfConnectionIsActiveDo(TheComplicatedInteraction); 
} 

    private void TheComplicatedInteraction() 
    {...} 

    private void IfConnectionIsActiveDo(Action action) 
    { 
     Status.Text = "Checking Connection"; 
     checkConnection.BeginInvoke(EndConnectionIsActive, 
     new object[] { checkConnection, action }); 
    } 

private void EndConnectionIsActive(IAsyncResult ar) 
{ 
    var delegates = (object[]) ar.AsyncState; 
    var is_active_delegate = (Func<bool>) delegates[0]; 
    var action = (Action) delegates[1]; 
    bool is_active=is_active_delegate.EndInvoke(ar); 
    IsConnected = is_active; 
    Dispatcher.Invoke(DispatcherPriority.Normal, 
     new Action<bool>(UpdateStatusBarToReflectConnectionAttempt),is_active); 
    if (is_active) action(); 
} 

我不是很滿意:它傳播的是曾經在兩個方法分爲五個(同步)代碼!這使得代碼非常混亂...

回答

3

這是因爲您正在等待循環完成操作。該循環在主線程上完成,因此UI被凍結。相反,您應該將AsyncCallback傳遞給BeginInvoke(而不是空),以便在操作完成時通知您。

這是我將如何實現它:

public void BeginConnectionIsActive() 
{ 
    AsyncCallback callback = (ar) => 
    { 
     bool result = checkConnection.EndInvoke(ar); 
     // Do something with the result 
    }; 
    checkConnection.BeginInvoke(callback,null); 
} 
+0

謝謝,我希望UI會神奇地響應Thread.Sleep期間的用戶操作,或者WPF的Application.DoEvents存在等價物。看來這是不好的解決方案 – Dabblernl 2009-09-13 18:36:38

2

我不能肯定地告訴您的代碼示例,您正在調用這一點,但在一般情況下,你應該永遠不要在UI上阻止工作線。正如你所提到的,這將導致UI變得無法響應。你必須在後臺完成這項工作。我的猜測是,你直接在UI線程上運行ConnectionIsActive循環,這意味着UI狀態更新將基本上被阻止,直到你從該方法返回。

它看起來像你有正確的想法(使用異步方法),但實現似乎過於複雜。

便捷地,即使服務器實現不使用異步方法,WCF也允許您擁有異步客戶端合同。該合同只需要具有相同的合同名稱和專門用於異步模式標記,如本例所示:

[ServiceContract(Name = "Ping")] 
interface IPing 
{ 
    [OperationContract(IsOneWay = false)] 
    void Ping(string data); 
} 

[ServiceContract(Name = "Ping")] 
interface IPingClient 
{ 
    [OperationContract(AsyncPattern = true, IsOneWay = false)] 
    IAsyncResult BeginPing(string data, AsyncCallback callback, object state); 

    void EndPing(IAsyncResult result); 
} 

現在在客戶端,您可以使用IPingClient合同,只需撥打client.BeginPing(...)。它將在所有實際工作都在後臺完成時立即返回,可選地在完成後回撥。然後

您的代碼可能是這個樣子:

void SomeCodeInUIThread() 
{ 
    // ... 
    // Do something to the UI to indicate it is 
    // checking the connection... 

    client.BeginPing("...some data...", this.OnPingComplete, client); 
} 

void OnPingComplete(IAsyncResult result) 
{ 
    IPingClient client = (IPingClient)result.AsyncState; 
    try 
    { 
     client.EndPing(result); 
     // ... 
    } 
    catch (TimeoutException) 
    { 
     // handle error 
    } 

    // Operation is complete, so update the UI to indicate 
    // you are done. NOTE: You are on a callback thread, so 
    // make sure to Invoke back to the main form. 
    // ... 
} 
+0

這種方法非常有趣。我試圖找到一些文檔,但我發現的只是:「運行svcutil.exe並使用異步選項」。我接受了托馬斯的回答,因爲它更一般。 – Dabblernl 2009-09-13 18:40:44

+0

我會從這裏開始:「同步和異步操作」http://msdn.microsoft.com/en-us/library/ms734701.aspx – bobbymcr 2009-09-13 19:16:36

1

使用這樣的事情:

public static class Run 
{ 
    public static void InBackround<TResult>(Func<TResult> operation, Action<TResult> after) 
    { 
     Async(operation, after, DispatcherPriority.Background); 
    } 

    private static void Async<TResult>(Func<TResult> operation, Action<TResult> after, 
               DispatcherPriority priority) 
    { 
     var disp = Dispatcher.CurrentDispatcher; 

     operation.BeginInvoke(delegate(IAsyncResult ar) 
     { 
      var result = operation.EndInvoke(ar); 
      disp.BeginInvoke(priority, after, result); 
     }, null); 
    } 
} 

,並調用它是這樣的:

Run.InBackround(CheckConnection, DoTheComplicatedInteraction); 

這需要一些代表,在後臺線程中調用它,並在完成後獲取返回值並在UI線程中調用'after'委託。

在上面的例子DoTheComplicatedInteraction應該是這樣的:

DoTheComplicatedInteraction(bool connected) { 
    if(connected) { proceed... } else { retry... } 
} 

如果你感到困惑調度閱讀Build More Responsive Apps With The Dispatcher文章MSDN上。

+1

謝謝你的回答。這看起來很複雜,我還沒有嘗試過。如果我沒有弄錯,它不會有條件地調用「after」代表。 – Dabblernl 2009-09-13 18:38:49

+0

對於模糊的重播感到抱歉,我添加了一些評論,並希望這有助於 – gimalay 2009-09-13 19:58:35

+0

非常感謝!我會看看它。 – Dabblernl 2009-09-13 21:14:41

相關問題