Pattern for Calling Wcf Service Using Async/Await

Pattern for calling WCF service using async/await

I think a feasible solution might be to use a custom awaiter to flow the new operation context via OperationContext.Current. The implementation of OperationContext itself doesn't appear to require thread affinity. Here is the pattern:

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

How to add async support to a .NET 4.5 WCF service so it doesn't disrupt existing clients?

As long as you rename your server side method to include the word XxxxxAsync it will not change the clientside signature.

WCF automaticly makes two endpoints for every method, a synchronous version and a async version. You can see this this with the WCF test client.

For example, the following service contract

[ServiceContract]
public interface IService1
{
[OperationContract]
string GetData(int value);
}

public class Service1 : IService1
{
public string GetData(int value)
{
return string.Format("You entered: {0}", value);
}
}

When you fire up the WCF test client you will see 2 methods available

Sample Image

If I change the code to the following

[ServiceContract]
public interface IService1
{
[OperationContract]
Task<string> GetDataAsync(int value);
}

public class Service1 : IService1
{
public async Task<string> GetDataAsync(int value)
{
await Task.Delay(value);
return string.Format("You entered and awaited: {0}", value);
}
}

I can still call the synchronous string GetData(int) method from my client

Sample Image

Note, you will not be able to use the same interface clientside and serverside anymore to represent the API (and you really shouldn't, the client side interface should have both versions in it. That way the client can decide if it wants to make a blocking call or not). You will still be able to use shared models between them however.

Implementing async/await pattern for manually generated WCF (client-side) proxies

If you're using ChannelFactory to allow for async-await your interface needs to return a Task or Task<T>.

It will force your server side to also return a task but you can do that synchronously with Task.CompletedTask and Task.FromResult if you insist on keeping it synchronous (though why would you if you have the option).

For example:

[ServiceContract]
interface IProductService
{
[OperationContract]
Task<Product> GetAsync(int id);
}

class ProductService : IProductService
{
ChannelFactory<IProductService> factory;

public ProductService()
{
factory = new ChannelFactory<IProductService>("*");
}

public Task<Product> GetAsync(int id)
{
var channel = factory.CreateChannel();
return channel.GetAsync(id);
}
}

class ProductAPI : IProductService
{
public Task<Product> GetAsync(int id) => Task.FromResult(Get(id))
}

How to call wcf service Asynchronously

I think the best way is to convert the APM pattern into the Task pattern, using Task.Factory.FromAsync:

public static class WcfExt
{
public static Task<int> AddAsync(this IAddTwoNumbers service, int a, int b)
{
return Task.Factory.FromAsync(
(asyncCallback, asyncState) =>
service.BeginAdd(a, b, asyncCallback, asyncState),
(asyncResult) =>
service.EndAdd(asyncResult), null);
}
}

Usage:

IAddTwoNumbers service = CreateWcfClientProxy();
int result = await service.AddAsync(a, b);

Async/Await WCF client in a PCL

In the end I generated the proxy and manually wrapped the generated APM model with the TaskFactory to generate an Async/Await pattern in the client within the PCL. I wrote it down in this blog post.

Edit: Updated broken link.

Call a WCF service Asynchronously and Wait for it in a New Task

What you try to achieve is a good thing. Not blocking some main thread is a good idea and the way you do it is going to work.

However, if you do it this way, you have to manually control the number of tasks. You probably do not want the number to go too high, otherwise too much of parallelism might hurt performance.

There are classes in .net that help to manage a queue of tasks by what is called a thread pool.
You configure the threadpool with a known maximum number of threads, and then just queue tasks to the pool. The threadpool class takes care of emptying the queue and assigning tasks to free threads in a pool.

You can learn more about thread pools here on ms docs

The sample below is from the MS docs site.

using System;
using System.Threading;

public class Example
{
public static void Main()
{
// Queue the task.
ThreadPool.QueueUserWorkItem(ThreadProc);
Console.WriteLine("Main thread does some work, then sleeps.");
Thread.Sleep(1000);

Console.WriteLine("Main thread exits.");
}

// This thread procedure performs the task.
static void ThreadProc(Object stateInfo)
{
// No state object was passed to QueueUserWorkItem, so stateInfo is null.
Console.WriteLine("Hello from the thread pool.");
}
}
// The example displays output like the following:
// Main thread does some work, then sleeps.
// Hello from the thread pool.
// Main thread exits.


Related Topics



Leave a reply



Submit