2009-09-11 3 views
1

wcf 서비스와 복잡한 상호 작용을하기 전에 서비스가 살아 있는지 간단한 확인을 수행하는 wcf 클라이언트가 있습니다. 실패한 연결의 시간 제한이 1 분이므로이 검사를 비동기 적으로 구현하려고합니다.다른 스레드에서 서버와 통신하는 동안 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을 사용하면 Main 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가 고정됩니다. 대신 AsyncCallbackBeginInvoke (null 대신)으로 전달해야 작업이 완료 될 때 알림을받을 수 있습니다. 여기

내가 그것을 구현하는 것이 방법은 다음과 같습니다

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

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(...)를 호출합니다. 모든 실제 작업이 백그라운드에서 완료되면 즉시 반환되며, 완료되면 선택적으로 다시 호출합니다.,

Run.InBackround(CheckConnection, DoTheComplicatedInteraction); 

그것은 어떤 대리자를 취 이런

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를 실행하고 async 옵션을 사용하십시오." 토마스의 답변은 더 일반적이기 때문에 받아 들였습니다. – Dabblernl

+0

여기서부터 시작하겠다 : "동기식 및 비동기식"http://msdn.microsoft.com/en-us/library/ms734701.aspx – bobbymcr

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); 
    } 
} 

을 그리고 다음과 같이 호출 백그라운드 스레드에서 호출하고 완료되면 반환 값을 가져오고 UI 스레드에서 '대리자'를 호출합니다. 예에서

는 DoTheComplicatedInteraction 이상으로해야 보이는 같은 : 당신이 디스패처에 대한 혼란 MSDN에 Build More Responsive Apps With The Dispatcher 기사를 읽으면

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

.

+1

답장을 보내 주셔서 감사합니다. 이것은 꽤 복잡한 것처럼 보입니다. 아직 시도하지 않았습니다. 내가 잘못하지 않으면 조건부로 "후"대리인을 호출하지 않습니다. – Dabblernl

+0

불명료 한 재연을 위해 유감스럽게 생각하고, 나는 몇몇 코멘트를 추가하고 이것이 돕 l 것을 희망한다 – gimalay

+0

많은 감사합니다! 나는 그것을 볼 것이다. – Dabblernl

관련 문제