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
.Net (C#): Getting Child Windows When You Only Have a Process Handle or Pid
Identityserver4 Role Based Authorization for Web API with ASP.NET Core Identity
Accessing UI Control from Backgroundworker Thread
Force Download of a File on Web Server - Asp .Net C#
Difference Between "Var" and "Object" in C#
Zooming Graphics Based on Current Mouse Position
C# Adding Button with Value at Runtime
Lambda Expression Not Returning Expected Memberinfo
Query Extremely Slow in Code But Fast in Ssms
How to Pass Parameters to the Custom Action
Event Bubbling and Mvp: ASP.NET
Why Does C# (4.0) Not Allow Co- and Contravariance in Generic Class Types
Combining Multiple Attributes to a Single Attribute - Merge Attributes