I created a proxy with task-based operations .
How to properly call this service (get rid of ServiceClient and OperationContext afterwards) using async / await?
My first attempt was:
public async Task<HomeInfo> GetHomeInfoAsync(DateTime timestamp) { using (var helper = new ServiceHelper<ServiceClient, ServiceContract>()) { return await helper.Proxy.GetHomeInfoAsync(timestamp); } }
Being a ServiceHelper class that creates ServiceClient and OperationContextScope and gets rid of them afterwards:
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; }
However, this failed miserably when two services were called simultaneously with the following error: "This OperationContextScope is located in a different thread than it was created."
MSDN says:
Do not use the asynchronous 'await' template in the OperationContextScope block. When a continuation occurs, it can be executed in another thread, and OperationContextScope depends on the specific thread. If you need to call 'await' for an asynchronous call, use it outside the OperationContextScope block.
So what a problem! But how can we fix it right?
This guy did just what MSDN says :
private async void DoStuffWithDoc(string docId) { var doc = await GetDocumentAsync(docId); if (doc.YadaYada) {
My problem with its code is that it never calls Close (or Abort) on the ServiceClient.
I also found a way to distribute OperationContextScope using a custom SynchronizationContext . But, besides the fact that it has a lot of βriskyβ code, he claims that:
It is worth noting that it has several small problems associated with deleting areas of the operation context (since they allow you to place them only in the calling thread), but this does not seem to be a problem, since (at least in accordance with the disassembly) ), they implement Dispose (), but not Finalize ().
So, are we out of luck here? Is there a proven pattern for invoking WCF services using async / await AND disposing of BOTH ServiceClient and OperationContextScope ? Maybe someone from Microsoft (maybe guru Stephen Taub :)) can help.
Thank!
[UPDATE]
With a lot of help from a Noseratio user, I came up with something that works: don't use OperationContextScope . If you use it for any of these reasons, try to find a workaround that matches your scenario. Otherwise, if you really need OperationContextScope , you will have to come up with a SynchronizationContext implementation that captures it, and it seems very complicated (if at all possible - there should be a reason why this is not the default behavior).
So, the full working code:
public async Task<HomeInfo> GetHomeInfoAsync(DateTime timestamp) { using (var helper = new ServiceHelper<ServiceClient, ServiceContract>()) { return await helper.Proxy.GetHomeInfoAsync(timestamp); } }
With ServiceHelper :
public class ServiceHelper<TServiceClient, TService> : IDisposable where TServiceClient : ClientBase<TService>, 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(); }
Note that the class supports the extension; you may need to inherit and provide credentials.
The only possible βerrorβ is that in GetHomeInfoAsync you cannot just return the Task that you get from the proxy server (which should seem natural, why create a new Task when you already have one). Well, in this case, you need to await Task proxy and then close (or abort) the ServiceClient , otherwise you will close it immediately after calling the service (while bytes are sent over the wire). )!
Well, we have a way to make this work, but it would be nice to get an answer from an authoritative source, as Noseratio claims.