Template for calling WCF service using async / wait

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) { // more code here } } public Task<Document> GetDocumentAsync(string docId) { var docClient = CreateDocumentServiceClient(); using (new OperationContextScope(docClient.InnerChannel)) { return docClient.GetDocumentAsync(docId); } } 

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

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.

+58
c # async-await wcf
Aug 17 '13 at 4:03
source share
8 answers

I think that the real solution could be to use a custom pending operation to transmit a new context through OperationContext.Current . The implementation of OperationContext itself does not seem to require thread binding. Here is a sample:

 async Task TestAsync() { using(var client = new WcfAPM.ServiceClient()) using (var scope = new FlowingOperationContextScope(client.InnerChannel)) { await client.SomeMethodAsync(1).ContinueOnScope(scope); await client.AnotherMethodAsync(2).ContinueOnScope(scope); } } 

Here is the implementation of FlowingOperationContextScope and ContinueOnScope (only slightly tested):

 public sealed class FlowingOperationContextScope : IDisposable { bool _inflight = false; bool _disposed; OperationContext _thisContext = null; OperationContext _originalContext = null; public FlowingOperationContextScope(IContextChannel channel): this(new OperationContext(channel)) { } public FlowingOperationContextScope(OperationContext context) { _originalContext = OperationContext.Current; OperationContext.Current = _thisContext = context; } public void Dispose() { if (!_disposed) { if (_inflight || OperationContext.Current != _thisContext) throw new InvalidOperationException(); _disposed = true; OperationContext.Current = _originalContext; _thisContext = null; _originalContext = null; } } internal void BeforeAwait() { if (_inflight) return; _inflight = true; // leave _thisContext as the current context } internal void AfterAwait() { if (!_inflight) throw new InvalidOperationException(); _inflight = false; // ignore the current context, restore _thisContext OperationContext.Current = _thisContext; } } // ContinueOnScope extension public static class TaskExt { public static SimpleAwaiter<TResult> ContinueOnScope<TResult>(this Task<TResult> @this, FlowingOperationContextScope scope) { return new SimpleAwaiter<TResult>(@this, scope.BeforeAwait, scope.AfterAwait); } // awaiter public class SimpleAwaiter<TResult> : System.Runtime.CompilerServices.INotifyCompletion { readonly Task<TResult> _task; readonly Action _beforeAwait; readonly Action _afterAwait; public SimpleAwaiter(Task<TResult> task, Action beforeAwait, Action afterAwait) { _task = task; _beforeAwait = beforeAwait; _afterAwait = afterAwait; } public SimpleAwaiter<TResult> GetAwaiter() { return this; } public bool IsCompleted { get { // don't do anything if the task completed synchronously // (we're on the same thread) if (_task.IsCompleted) return true; _beforeAwait(); return false; } } public TResult GetResult() { return _task.Result; } // INotifyCompletion public void OnCompleted(Action continuation) { _task.ContinueWith(task => { _afterAwait(); continuation(); }, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, SynchronizationContext.Current != null ? TaskScheduler.FromCurrentSynchronizationContext() : TaskScheduler.Current); } } } 
+30
Mar 31 '14 at 3:12
source share

A simple way is to move the wait outside the used block.

 public Task<Document> GetDocumentAsync(string docId) { var docClient = CreateDocumentServiceClient(); using (new OperationContextScope(docClient.InnerChannel)) { var task = docClient.GetDocumentAsync(docId); } return await task; } 
+5
Dec 15 '16 at 3:37
source share

I decided to write my own code that helps with this, posting in case this helps anyone. It seems like you need to make a little less mistake (unforeseen races, etc.) against the implementation of SimpleAwaiter above, but you will be the judge:

 public static class WithOperationContextTaskExtensions { public static ContinueOnOperationContextAwaiter<TResult> WithOperationContext<TResult>(this Task<TResult> @this, bool configureAwait = true) { return new ContinueOnOperationContextAwaiter<TResult>(@this, configureAwait); } public static ContinueOnOperationContextAwaiter WithOperationContext(this Task @this, bool configureAwait = true) { return new ContinueOnOperationContextAwaiter(@this, configureAwait); } public class ContinueOnOperationContextAwaiter : INotifyCompletion { private readonly ConfiguredTaskAwaitable.ConfiguredTaskAwaiter _awaiter; private OperationContext _operationContext; public ContinueOnOperationContextAwaiter(Task task, bool continueOnCapturedContext = true) { if (task == null) throw new ArgumentNullException("task"); _awaiter = task.ConfigureAwait(continueOnCapturedContext).GetAwaiter(); } public ContinueOnOperationContextAwaiter GetAwaiter() { return this; } public bool IsCompleted { get { return _awaiter.IsCompleted; } } public void OnCompleted(Action continuation) { _operationContext = OperationContext.Current; _awaiter.OnCompleted(continuation); } public void GetResult() { OperationContext.Current = _operationContext; _awaiter.GetResult(); } } public class ContinueOnOperationContextAwaiter<TResult> : INotifyCompletion { private readonly ConfiguredTaskAwaitable<TResult>.ConfiguredTaskAwaiter _awaiter; private OperationContext _operationContext; public ContinueOnOperationContextAwaiter(Task<TResult> task, bool continueOnCapturedContext = true) { if (task == null) throw new ArgumentNullException("task"); _awaiter = task.ConfigureAwait(continueOnCapturedContext).GetAwaiter(); } public ContinueOnOperationContextAwaiter<TResult> GetAwaiter() { return this; } public bool IsCompleted { get { return _awaiter.IsCompleted; } } public void OnCompleted(Action continuation) { _operationContext = OperationContext.Current; _awaiter.OnCompleted(continuation); } public TResult GetResult() { OperationContext.Current = _operationContext; return _awaiter.GetResult(); } } } 

Usage (little guidance and nesting not tested ...):

  /// <summary> /// Make a call to the service /// </summary> /// <param name="action"></param> /// <param name="endpoint"> </param> public async Task<ResultCallWrapper<TResult>> CallAsync<TResult>(Func<T, Task<TResult>> action, EndpointAddress endpoint) { using (ChannelLifetime<T> channelLifetime = new ChannelLifetime<T>(ConstructChannel(endpoint))) { // OperationContextScope doesn't work with async/await var oldContext = OperationContext.Current; OperationContext.Current = new OperationContext((IContextChannel)channelLifetime.Channel); var result = await action(channelLifetime.Channel) .WithOperationContext(configureAwait: false); HttpResponseMessageProperty incomingMessageProperty = (HttpResponseMessageProperty)OperationContext.Current.IncomingMessageProperties[HttpResponseMessageProperty.Name]; string[] keys = incomingMessageProperty.Headers.AllKeys; var headersOrig = keys.ToDictionary(t => t, t => incomingMessageProperty.Headers[t]); OperationContext.Current = oldContext; return new ResultCallWrapper<TResult>(result, new ReadOnlyDictionary<string, string>(headersOrig)); } } 
+2
May 26 '15 at 16:11
source share

Asynchronous stream is supported from .Net 4.6.2.

We have an ASP.Net WebApi application running on .Net 4.6 where we used the accepted answer. TaskScheduler.FromCurrentSynchronizationContext() caused deadlock problems when the current synchronization context is AspNetSynchronizationContext .

I believe that the continuation task was queued after the actual task, as a result of which the real task expects to continue, while the continuation task must be completed in order to complete the actual task. that is, tasks are waiting for each other.

Therefore, I fixed the problem by changing the TaskAwaiter task using the continue task. See: https://blogs.msdn.microsoft.com/lucian/2012/12/11/how-to-write-a-custom-awaiter/

+2
Nov 07 '18 at 19:57
source share

It has been a while on this, but I will intervene with my own home decision.

If someone does not mind doing without OperationContextScope , you can consider something like this:

Extension Methods

 using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Security; using System.Text; using System.Threading.Tasks; namespace Intexx.ServiceModel { public static class WcfExtensions { [DebuggerStepThrough] public static void Call<TChannel>(this TChannel Client, Action<TChannel> Method) where TChannel : ICommunicationObject { try { Method.Invoke(Client); } finally { Cleanup(Client); } } [DebuggerStepThrough] public static TResult Call<TChannel, TResult>(this TChannel Client, Func<TChannel, TResult> Method) where TChannel : ICommunicationObject { try { return Method.Invoke(Client); } finally { Cleanup(Client); } } [DebuggerStepThrough] public async static Task CallAsync<TChannel>(this TChannel Client, Func<TChannel, Task> Method) where TChannel : ICommunicationObject { try { await Method.Invoke(Client); } finally { Cleanup(Client); } } [DebuggerStepThrough] public async static Task<TResult> CallAsync<TChannel, TResult>(this TChannel Client, Func<TChannel, Task<TResult>> Method) where TChannel : ICommunicationObject { try { return await Method.Invoke(Client); } finally { Cleanup(Client); } } private static void Cleanup<TChannel>(TChannel Client) where TChannel : ICommunicationObject { try { if (Client.IsNotNull) { if (Client.State == CommunicationState.Faulted) Client.Abort(); else Client.Close(); } } catch (Exception ex) { Client.Abort(); if (!ex is CommunicationException && !ex is TimeoutException) throw new Exception(ex.Message, ex); } finally { Client = null; } } } } 

Client class

 using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Security; using System.Text; using System.Threading.Tasks; namespace Reader { public class Client { public static CemReaderClient Create() { Tuple<Channels.Binding, EndpointAddress, double> oService; try { oService = Main.Services(typeof(ICemReader)); return new CemReaderClient(oService.Item1, oService.Item2); } catch (KeyNotFoundException ex) { return null; } } } } 

Usage (in VB, since the code will not be converted)

 Using oReader As Reader.CemReaderClient = Reader.Client.Create If oReader.IsNotNothing Then Dim lIsReading = Await oReader.CallAsync(Function(Reader As Reader.CemReaderClient) Me.ConfigFilePath = If(Me.ConfigFilePath, Reader.GetConfigFilePath) Me.BackupDrive = If(Me.BackupDrive, Reader.GetBackupDrive) Me.SerialPort = If(Me.SerialPort, Reader.GetSerialPort) Me.LogFolder = If(Me.LogFolder, Reader.GetLogFolder) Return Reader.GetIsReadingAsync End Function) End If End Using 

This worked for me reliably in production at frequency loads of about 15 calls / second on the client side (this would be as fast as sequential processing would allow). It was on a single thread, though - it has not been thoroughly tested for thread safety. YMMV.

In my case, I decided to roll extension methods into their own private NuGet package. The whole structure turned out to be very convenient.

Of course, this will have to be redefined if OperationContextScope ever needed.

The Tuple bit in the Client class is designed to support service discovery. If someone would also like to see this code, write to me and I will update my answer.

+1
Aug 14 '19 at 3:43
source share

I'm a little confused, I found this blog: Asynchronous task-based operation in WCF

Here is the async wcf post:

 [ServiceContract] public interface IMessage { [OperationContract] Task<string> GetMessages(string msg); } public class MessageService : IMessage { async Task<string> IMessage.GetMessages(string msg) { var task = Task.Factory.StartNew(() => { Thread.Sleep(10000); return "Return from Server : " + msg; }); return await task.ConfigureAwait(false); } } 

Client:

 var client = new Proxy("BasicHttpBinding_IMessage"); var task = Task.Factory.StartNew(() => client.GetMessages("Hello")); var str = await task; 

So is that a good way too?

0
Jul 25 '17 at 8:06
source share

I ran into the same problem, but it became clear to me that I did not need to use async / wait at all.

Since you are not processing the result after processing, there is no need to wait for a response. If you need to process the result, just use the old TPL extension.

 public Task<MyDomainModel> GetHomeInfoAsync(DateTime timestamp) { using (var helper = new ServiceHelper<ServiceClient, ServiceContract>()) { return helper.Proxy.GetHomeInfoAsync(timestamp).ContinueWith(antecedent=>processReplay(antecedent.Result)); } } 
-one
Mar 02 '14 at 16:25
source share

I do not know if this helps, but having seen this question in my search, answering the same question, I came across this .

From this, I should think that your code should look something like this:

 public async Task<HomeInfo> GetHomeInfoAsync(DateTime timestamp) { using (var client = CreateDocumentServiceClient()) { await client.BeginGetHomeInfoAsync(timestamp); } } 

I understand that my answer comes quite late: P, but it can help someone else.

-one
Aug 6 '14 at 10:53 on
source share



All Articles