How to Make a Call to My Wcf Service Asynchronous

How to make a call to my WCF service asynchronous?

All your needs will be satisfied in the following articles from MSDN:

Implementing an Async Service Operation

Calling WCF Service Async

Designing Service Contracts

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

WCF service call async function

With async/await you need to make it async all the way through.

Make the service a Task-based asynchronous service

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

With that, it is a simple matter of making the rest of the code async all the way through.

public class http {
public async Task<string> HttpRequestAsync() {
var request = GetHttpRequestMessage();
string str1 = await ExecuteRequest(request);
Console.WriteLine(str1);
return str1;
}

//...code removed for brevity as they are already Task-based
}

This should now allow the function to be used in the service implementation

public class Service1 : IService1 {
public Task<string> GetData() {
http test = new http();
return test.HttpRequestAsync();
}
}

In the original example provided the code was mixing async and blocking calls .Result, which can lead to deadlocks

Reference Async/Await - Best Practices in Asynchronous Programming

I would also advise making the HttpClient static and reuse it rather than creating multiple instances and disposing of them.

Reference keep an instance of HttpClient for the lifetime of your application for each distinct API that you connect to.

UPDATE:

Another possibility is that the URL being called is HTTPS.

Consider applying the following before making the request via the HttpClient

//Handle TLS protocols
System.Net.ServicePointManager.SecurityProtocol =
System.Net.SecurityProtocolType.Tls
| System.Net.SecurityProtocolType.Tls11
| System.Net.SecurityProtocolType.Tls12;

Call asynchronous method on a WCF service with Single InstanceContextMode

According to MSDN:

If the InstanceContextMode value is set to Single the result is that your service can only process one message at a time unless you also set the ConcurrencyMode value to ConcurrencyMode.

(looks like they forgot to tell what ConcurrencyMode)

So just set the right ConcurrencyMode on your service:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]

Make sure your code is stateless and thread-safe though. This combination is very error prone.

How can I asynchronously call a WCF service?

You need to call someService.EndSomeMethod(asyncResult) in your callback to get the service result. This is actually part of the standard APM pattern. You'd have to do the same thing with say a FileStream.

Following this, result is null

Yes, because CompletedAsnycResult only exists on the server. Your client can get any other type returned by the runtime. IAsyncResult is not part of the WCF wire-contract.

In practice, asyncResult will be an instance of some WCF-internal class.

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.

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


Related Topics



Leave a reply



Submit