2014-09-30 139 views
0

我有一種感覺,要有比我想出的更好的解決方案;這裏的問題:WPF/WCF異步服務調用和同步上下文

WPF窗體將調用一個WCF方法,它返回一個布爾值。調用本身不應該在UI線程上,並且調用的結果將需要顯示在窗體上,所以返回應該被封送回UI線程。

在這個例子中,我創建了一個「ServiceGateway」類,表單將在完成登錄操作後傳遞一個方法來執行。網關應該使用UI SynchronizationContext調用這個Login-complete委託,這是在從窗體實例化網關時傳遞的。 Login方法使用anon調用_proxy.Login。異步委託,然後提供了調用委託使用UI的SynchronizationContext(「回調」 PARAM)提供給網關(從表格)的回調:

[CallbackBehavior(UseSynchronizationContext = false)] 
public class ChatServiceGateway : MessagingServiceCallback 
{ 

    private MessagingServiceClient _proxy; 
    private SynchronizationContext _uiSyncContext; 

    public ChatServiceGateway(SynchronizationContext UISyncContext) 
    { 
     _proxy = new MessagingServiceClient(new InstanceContext(this)); 
     _proxy.Open(); 
     _uiSyncContext = UISyncContext; 
    } 


    public void Login(String UserName, Action<bool> callback) 
    { 
     new Func<bool>(() => _proxy.Login(UserName)).BeginInvoke(delegate(IAsyncResult result) 
     { 
      bool LoginResult = ((Func<bool>)((AsyncResult)result).AsyncDelegate).EndInvoke(result); 
      _uiSyncContext.Send(new SendOrPostCallback(obj => callback(LoginResult)), null); 
     }, null); 

    } 

的登錄方法是從表單調用響應於一個按鈕點擊事件。

這工作正常,但我有一個懷疑,我正在以錯誤的方式去登錄方法;尤其是因爲我必須對WCF服務的任何其他方法調用執行相同的操作,而且這很醜陋。

我想保持封裝在網關中的異步行爲和ui同步。在WCF端實現異步行爲會更好嗎?基本上我很感興趣,如果我可以更普遍地爲其他方法實現上面的代碼,或者如果有更好的方法在一起。

+1

你應該可以用'基於任務'的異步API來重新創建WCF客戶端,這會很好地清除它。 – 2014-10-01 01:06:19

回答

1

只要你的目標至少是VS 2012和.NET 4.5,async/await就是要走的路。請注意缺少SynchronizationContext參考號 - 它在await之前的屏蔽下捕獲,並在異步操作完成後回發。

public async Task Login(string userName, Action<bool> callback) 
{ 
    // The delegate passed to `Task.Run` is executed on a ThreadPool thread. 
    bool loginResult = await Task.Run(() => _proxy.Login(userName)); 

    // OR 
    // await _proxy.LoginAsync(UserName); 
    // if you have an async WCF contract. 

    // The callback is executed on the thread which called Login. 
    callback(loginResult); 
} 

Task.Run主要用於推動CPU限制的工作線程池,所以上面的例子也有點濫用它,但如果你不希望重寫由MessagingServiceClient實現使用異步Task合同 - 這是一個很好的方法。

還是.NET 4.0的方式(不async/await支持):

public Task Login(string userName, Action<bool> callback) 
{ 
    // The delegate passed to `Task.Factory.StartNew` 
    // is executed on a ThreadPool thread. 
    var task = Task.Factory.StartNew(() => _proxy.Login(userName)); 

    // The callback is executed on the thread which called Login. 
    var continuation = task.ContinueWith(
     t => callback(t.Result), 
     TaskScheduler.FromCurrentSynchronizationContext() 
    ); 

    return continuation; 
} 

這一點從你目前正在做的事情的方式離開的,因爲它是調用者的責任以確保在UI線程上被調用,如果他們想要在其上執行回調。然而,這是標準的做法,當談到async,而你可以保持到UI SynchronizationContextTaskScheduler參考作爲您的ChatServiceGateway回調/繼續執行正確的線程上,它將部分吹出你的實現和個人(這只是我的意見)我會說這是一個代碼味道。

+0

完美,正是我想知道的。有些事情對我的做法看起來非常不利,肯定有趣。謝謝! – 2014-10-01 02:27:08

+0

非常歡迎。我想補充的最後一件事是:請注意Stephen Cleary在他對這個問題的評論中所說的話。如果您可以使用包含「基於任務」的異步方法重新生成WCF代理,那麼您的網關類可以簡化爲超級精簡抽象或完全消除。 – 2014-10-01 03:33:51