Modello per chiamare il servizio WCF usando async / await

Ho generato un proxy con operazioni basate sulle attività .

Come dovrebbe essere invocato correttamente questo servizio (smaltimento di ServiceClient e OperationContext seguito) utilizzando async / await?

Il mio primo tentativo è stato:

 public async Task GetHomeInfoAsync(DateTime timestamp) { using (var helper = new ServiceHelper()) { return await helper.Proxy.GetHomeInfoAsync(timestamp); } } 

Essere ServiceHelper una class che crea ServiceClient e OperationContextScope e li ServiceHelper in seguito:

 try { if (_operationContextScope != null) { _operationContextScope.Dispose(); } if (_serviceClient != null) { if (_serviceClient.State != CommunicationState.Faulted) { _serviceClient.Close(); } else { _serviceClient.Abort(); } } } catch (CommunicationException) { _serviceClient.Abort(); } catch (TimeoutException) { _serviceClient.Abort(); } catch (Exception) { _serviceClient.Abort(); throw; } finally { _operationContextScope = null; _serviceClient = null; } 

Tuttavia, questo ha fallito miseramente quando si chiamano due servizi contemporaneamente con il seguente errore: “Questo OperationContextScope è disposto su un thread diverso da quello che è stato creato.”

MSDN dice:

Non utilizzare il pattern di attesa “asincrono” all’interno di un blocco OperationContextScope. Quando si verifica la continuazione, può essere eseguito su un thread diverso e OperationContextScope è thread specifico. Se è necessario chiamare “attendere” per una chiamata asincrona, utilizzarla al di fuori del blocco OperationContextScope.

Quindi questo è il problema! Ma come lo ripariamo correttamente?

Questo ragazzo ha fatto esattamente quello che dice MSDN :

 private async void DoStuffWithDoc(string docId) { var doc = await GetDocumentAsync(docId); if (doc.YadaYada) { // more code here } } public Task GetDocumentAsync(string docId) { var docClient = CreateDocumentServiceClient(); using (new OperationContextScope(docClient.InnerChannel)) { return docClient.GetDocumentAsync(docId); } } 

Il mio problema con il suo codice è che non chiama mai Close (o Abort) sul ServiceClient.

Ho anche trovato un modo per propagare OperationContextScope utilizzando un SynchronizationContext personalizzato. Ma, oltre al fatto che è un codice molto “rischioso”, afferma che:

Vale la pena notare che ha alcuni piccoli problemi riguardanti lo smaltimento degli ambiti contesto-operazione (poiché consentono solo di disporli sul thread chiamante), ma questo non sembra essere un problema dal momento che (almeno secondo lo sassembly), implementano Dispose () ma non Finalize ().

Quindi, siamo sfortunati qui? Esiste un modello comprovato per chiamare i servizi WCF usando async / await e smaltendo ENTRAMBI il ServiceClient e l’ OperationContextScope ? Forse qualcuno che forma Microsoft (forse il guru Stephen Toub :)) può aiutare.

Grazie!

[AGGIORNARE]

Con molto aiuto da parte dell’utente Noseratio, mi è venuto in mente qualcosa che funzioni: non usare OperationContextScope . Se lo utilizzi per uno qualsiasi di questi motivi, prova a trovare una soluzione che si adatti al tuo scenario. Altrimenti, se davvero, davvero, hai bisogno di OperationContextScope , dovrai realizzare un’implementazione di un SynchronizationContext che lo catturi, e ciò sembra molto difficile (se ansible – ci deve essere una ragione per cui questa non è la comportamento predefinito).

Quindi, il codice completo funzionante è:

 public async Task GetHomeInfoAsync(DateTime timestamp) { using (var helper = new ServiceHelper()) { return await helper.Proxy.GetHomeInfoAsync(timestamp); } } 

Con ServiceHelper è:

 public class ServiceHelper : IDisposable where TServiceClient : ClientBase, new() where TService : class { protected bool _isInitialized; protected TServiceClient _serviceClient; public TServiceClient Proxy { get { if (!_isInitialized) { Initialize(); _isInitialized = true; } else if (_serviceClient == null) { throw new ObjectDisposedException("ServiceHelper"); } return _serviceClient; } } protected virtual void Initialize() { _serviceClient = new TServiceClient(); } // Implement IDisposable. // Do not make this method virtual. // A derived class should not be able to override this method. public void Dispose() { Dispose(true); // Take yourself off the Finalization queue // to prevent finalization code for this object // from executing a second time. GC.SuppressFinalize(this); } // Dispose(bool disposing) executes in two distinct scenarios. // If disposing equals true, the method has been called directly // or indirectly by a user's code. Managed and unmanaged resources // can be disposed. // If disposing equals false, the method has been called by the // runtime from inside the finalizer and you should not reference // other objects. Only unmanaged resources can be disposed. protected virtual void Dispose(bool disposing) { // If disposing equals true, dispose all managed // and unmanaged resources. if (disposing) { try { if (_serviceClient != null) { if (_serviceClient.State != CommunicationState.Faulted) { _serviceClient.Close(); } else { _serviceClient.Abort(); } } } catch (CommunicationException) { _serviceClient.Abort(); } catch (TimeoutException) { _serviceClient.Abort(); } catch (Exception) { _serviceClient.Abort(); throw; } finally { _serviceClient = null; } } } } 

Si noti che la class supporta l’estensione; forse è necessario ereditare e fornire credenziali.

L’unico ansible “getcha” è che in GetHomeInfoAsync , non puoi semplicemente restituire l’ Task che ottieni dal proxy (che dovrebbe sembrare naturale, perché creare una nuova Task quando ne hai già una). Bene, in questo caso è necessario await l’ Task del proxy e quindi chiudere (o interrompere) il ServiceClient , altrimenti lo si chiuderà subito dopo aver richiamato il servizio (mentre i byte vengono inviati sul filo)!

OK, abbiamo un modo per farlo funzionare, ma sarebbe bello avere una risposta da una fonte autorevole, come afferma Noseratio.