.NET client-side WCF with queued requests - c#

Background
I'm working on updating legacy software library. The legacy code uses an infinitely looping System.Threading.Thread that executes processes in the queue. These processes perform multiple requests with another legacy system that can only process one request at a time.
I'm trying to modernize, but I'm new to WCF services and there may be a big hole in my knowledge that'd simplify things.
WCF Client-Side Host
In modernizing, I'm trying to move to a client-side WCF service. The WCF service allows requests to be queued from multiple a applications. The service takes a request and returns a GUID back so that I can properly associate via the callbacks.
public class SomeService : ISomeService
{
public Guid AddToQueue(Request request)
{
// Code to add the request to a queue, return a Guid, etc.
}
}
public interface ISomeCallback
{
void NotifyExecuting(Guid guid)
void NotifyComplete(Guid guid)
void NotifyFault(Guid guid, byte[] data)
}
WCF Client Process Queues
The problem I'm having is that the legacy processes can include more than one request. Process 1 might do Request X then Request Y, and based on those results follow up with Request Z. With the legacy system, there might be Processes 1-10 queued up.
I have a cludgy model where the process is executed. I'm handling events on the process to know when it's finished or fails. But, it just feels really cludgy...
public class ActionsQueue
{
public IList<Action> PendingActions { get; private set; }
public Action CurrentAction { get; private set; }
public void Add(Action action)
{
PendingAction.Add(action)
if (CurrentAction is null)
ExecuteNextAction();
}
private void ExecuteNextAction()
{
if (PendingActions.Count > 0)
{
CurrentAction = PendingActions[0];
PendingActions.RemoveAt(0);
CurrentAction.Completed += OnActionCompleted;
CurrentAction.Execute();
}
}
private OnActionCompleted(object sender, EventArgs e)
{
CurrentAction = default;
ExecuteNextAction();
}
}
public class Action
{
internal void Execute()
{
// Instantiate the first request
// Add handlers to the first request
// Send it to the service
}
internal void OnRequestXComplete()
{
// Use the data that's come back from the request
// Proceed with future requests
}
}
With the client-side callback the GUID is matched up to the original request, and it raises a related event on the original requests. Again, the implementation here feels really cludgy.
I've seen example of Async methods for the host, having a Task returned, and then using an await on the Task. But, I've also seen recommendations not to do this.
Any recommendations on how to untangle this mess into something more usable are appreciated. Again, it's possible that there's a hole in my knowledge here that's keeping me from a better solutiong.
Thanks

Queued communication between the client and the server of WCF is usually possible using a NetMsmqbinding, which ensures persistent communication between the client and the server. See this article for specific examples.
If you need efficient and fast message processing, use a non-transactional queue and set the ExactlyOnce attribute to False, but this has a security impact. Check this docs for further info.

In case anyone comes along later with a similar issue, this is a rough sketch of what I ended up with:
[ServiceContract(Name="MyService", SessionMode=Session.Required]
public interface IMyServiceContract
{
[OperationContract()]
Task<string> ExecuteRequestAsync(Action action);
}
public class MyService: IMyServiceContract
{
private TaskQueue queue = new TaskQueue();
public async Task<string> ExecuteRequestAsync(Request request)
{
return await queue.Enqueue(() => request.Execute());
}
}
public class TaskQueue
{
private SemaphoreSlim semaphore;
public TaskQueue()
{
semaphore = new SemaphoreSlim(1);
}
Task<T> Enqueue<T>(Func<T> function)
{
await semaphore.WaitAsync();
try
{
return await Task.Factory.StartNew(() => function.invoke();)
}
finally
{
semaphore.Release();
}
}
}

Related

Publish/Subcribe Notification Service Over gRPC Using protobuf-net.grpc

I'm trying to write a notification system between a server and multiple clients using gRPC server streaming in protobuf-net.grpc (.NET Framework 4.8).
I based my service off of this example. However, if I understand the example correctly, it is only able to handle a single subscriber (as _subscriber is a member variable of the StockTickerService class).
My test service looks like this:
private readonly INotificationService _notificationService;
private readonly Channel<Notification> _channel;
public ClientNotificationService(INotificationService notificationService)
{
_notificationService = notificationService;
_notificationService.OnNotification += OnNotification;
_channel = Channel.CreateUnbounded<Notification>();
}
private async void OnNotification(object sender, Notification notification)
{
await _channel.Writer.WriteAsync(notification);
}
public IAsyncEnumerable<Notification> SubscribeAsync(CallContext context = default)
{
return _channel.AsAsyncEnumerable(context.CancellationToken);
}
INotificationService just has an event OnNotification, which is fired when calling its Notify method.
I then realized that System.Threading.Channels implements the Producer/Consumer pattern, but I need the Publisher/Subscriber pattern. When trying it out, indeed only one of the clients gets notified, instead of all of them.
It would also be nice if the server knew when a client disconnects, which seems impossible when returning _channel.AsAsyncEnumerable.
So how can I modify this in order to
serve multiple clients, with all of them being notified when OnNotification is called
and log when a client disconnects?
For 1, you'd need an implementation of a publisher/subscriber API; each call to SubscribeAsync will always represent a single conversation between gRPC endpoints, so you'll need your own mechanism for broadcasting that to multiple consumers. Maybe RX is worth investigating there
For 2, context.CancellationToken should be triggered by client-disconnect
Many thanks to Marc Gravell
I rewrote the NotificationService like this, using System.Reactive.Subjects (shortened) - no need for an event, use an Action instead:
public class NotificationService<T>
{
private readonly Subject<T> _stream = new Subject<T>();
public void Publish(T notification)
{
_stream.OnNext(notification);
}
public IDisposable Subscribe(Action<T> onNext)
return _stream.Subscribe(onNext);
}
}
My updated ClientNotificationService, which is exposed as a code-first gRPC service:
public class ClientNotificationService : IClientNotificationService
{
private readonly INotificationService<Notification> _notificationService;
public ClientNotificationService(INotificationService<Notification> notificationService)
{
_notificationService = notificationService;
}
public async IAsyncEnumerable<Notification> SubscribeAsync(CallContext context = default)
{
try
{
Channel<Notification> channel = Channel.CreateUnbounded<Notification>(
new UnboundedChannelOptions { SingleReader = true, SingleWriter = true });
CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(context.CancellationToken);
using (_notificationService.Subscribe(n => channel.Writer.WriteAsync(n, cts.Token)))
{
await foreach (Notification notification in channel.AsAsyncEnumerable(cts.Token))
{
yield return notification;
}
}
}
finally
{
// canceled -> log, cleanup, whatever
}
}
}
Note: Solution provided by OP on question section.

Consumer Producer- Producer thread never executes assigned function

I have .NET Core Web API solution. In each call, I need to perform some database operations. The issue is at a time multiple db connections get opened & close. So to avoid it, I want to implement Queue of objects to be sent to database and then want a separate thread to perform db operation.
I've tried some code as below. But here, Consumer thread never executes assigned function. There is no separate thread for Producer, I am simply feeding queue with object.
What modifications I should do? Need some guidance as I'm new to Threading stuff.
public static class BlockingQueue
{
public static Queue<WebServiceLogModel> queue;
static BlockingQueue()
{
queue = new Queue<WebServiceLogModel>();
}
public static object Dequeue()
{
lock (queue)
{
while (queue.Count == 0)
{
Monitor.Wait(queue);
}
return queue.Dequeue();
}
}
public static void Enqueue(WebServiceLogModel webServiceLog)
{
lock (queue)
{
queue.Enqueue(webServiceLog);
Monitor.Pulse(queue);
}
}
public static void ConsumerThread(IConfiguration configuration)
{
WebServiceLogModel webServiceLog = (WebServiceLogModel)Dequeue();
webServiceLog.SaveWebServiceLog(configuration);
}
public static void ProducerThread(WebServiceLogModel webServiceLog)
{
Enqueue(webServiceLog);
Thread.Sleep(100);
}
}
I've created and started thread in StartUp.cs:
public Startup(IConfiguration configuration)
{
Thread t = new Thread(() => BlockingQueue.ConsumerThread(configuration));
t.Start();
}
In Controller, I've written code to feed the queue:
[HttpGet]
[Route("abc")]
public IActionResult GetData()
{
BlockingQueue.ProducerThread(logModel);
return StatusCode(HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound, ApplicationConstants.Message.NoBatchHistoryInfo);
}
First of all, try to avoid static classes and methods. Use pattern singleton in that case (and if you really need this).
Second, try to avoid lock, Monitor - those concurrency primitives significantly lower your performance.
In such situation, you can use BlockingCollection<> as 'Adam G' mentioned above, or you can develop your own solution.
public class Service : IDisposable
{
private readonly BlockingCollection<WebServiceLogModel> _packets =
new BlockingCollection<WebServiceLogModel>();
private Task _task;
private volatile bool _active;
private static readonly TimeSpan WaitTimeout = TimeSpan.FromSeconds(1);
public Service()
{
_active = true;
_task = ExecTaskInternal();
}
public void Enqueue(WebServiceLogModel model)
{
_packets.Add(model);
}
public void Dispose()
{
_active = false;
}
private async Task ExecTaskInternal()
{
while (_active)
{
if (_packets.TryTake(out WebServiceLogModel model))
{
// TODO: whatever you need
}
else
{
await Task.Delay(WaitTimeout);
}
}
}
}
public class MyController : Controller
{
[HttpGet]
[Route("abc")]
public IActionResult GetData([FromServices] Service service)
{
// receive model form somewhere
WebServiceLogModel model = FetchModel();
// enqueue model
service.Enqueue(model);
// TODO: return what you need
}
}
And in Startup:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<Service>();
// TODO: other init staffs
}
}
You even can add Start/Stop methods to the service instead of implementing IDisposable and start your service in the startup class in the method Configure(IApplicationBuilder app).
I think your consumer thread is executed just once if there is something in the queue and then immediately returns. If you want to have a thread doing work in background, which is started just once, it should never return and should catch all exceptions. Your thread from BlockingQueue.ConsumerThread is invoked once in Stratup and returns.
Also please be aware that doing such solution is not safe. ASP.NET doesn't guarantee background threads to be running if there are no requests coming in. Your application pool can recycle (and by default it recycles after 20 minutes of inactivity or every 27 hours), so there is a chance that your background code won't be executed for some queue items.
Also, while it doesn't solve all issues, I would suggest using https://www.hangfire.io/ to do background tasks in ASP.NET server. It has persistence layer, can retry jobs and has simple API's. In your request handler you can push new jobs to Hangfire and then have just 1 job processor thread.

Communication between Topshelf service (acting as TCP server) and selfhosted OWIN WebAPI

I have a Topshelf windows service that acts as a TCP server. Inside this service, I also have a self-hosted (OWIN) WebAPI.
My goal is to somehow allow the WebAPI to communicate with the TCP server that's contained and running in the same service. Naturally I could simply use something like a "trigger" file or a shared DB that could be polled frequently, though I'd like to know of any more optimal/native ways to achieve this.
To get a better idea of the project, think of a single page application consuming my API and making certain calls with arbitrary string parameters. This data should then be passed to clients (C++ console apps using winsock) that are connected to the running TCP server.
The following Container is instantiated and passed to the Topshelf HostConfigurator
class ContainerService
{
private APIService _apiService;
private EngineService _engineService;
protected IDisposable WebAppHolder { get; set; }
public bool Start(HostControl hostControl)
{
var host = hostControl;
_apiService = new APIService();
_engineService = new EngineService();
// Initialize API service
if (WebAppHolder == null)
{
WebAppHolder = _apiService.Initialize();
}
// Initialize Engine service
_engineService.Initialize();
return true;
}
public bool Stop(HostControl hostControl)
{
// Stop API service
if (WebAppHolder != null)
{
WebAppHolder.Dispose();
WebAppHolder = null;
}
// Stop Engine service
_engineService.Stop();
return true;
}
}
Standard Topshelf stuff in program entry point (as mentioned above):
HostFactory.Run(hostConfigurator =>
{
hostConfigurator.Service<ContainerService>(containerService =>
{
containerService.WhenStarted((service, control) => service.Start(control));
containerService.WhenStopped((service, control) => service.Stop(control));
});
hostConfigurator.RunAsLocalSystem();
hostConfigurator.SetServiceName("Educe Service Host");
hostConfigurator.SetDisplayName("Communication Service");
hostConfigurator.SetDescription("Responsible for API and Engine services");
});
TCP Server:
public void Initialize()
{
_serverListener = new TcpListener(new IPEndPoint(hostAddress, (int)port));
_serverListener.Start();
_threadDoBeginAcceptTcpClient = new Thread(() => DoBeginAcceptTcpClient(_serverListener));
_threadDoBeginAcceptTcpClient.Start();
}
...
public void DoBeginAcceptTcpClient(TcpListener listener)
{
while(!_breakThread)
{
// Set the event to nonsignaled state.
TcpClientConnected.Reset();
// Start to listen for connections from a client.
Console.WriteLine("Waiting for a connection...");
// Accept the connection.
listener.BeginAcceptTcpClient(DoAcceptTcpClientCallback, listener);
// Wait until a connection is made and processed before continuing.
TcpClientConnected.WaitOne();
}
}
// Process the client connection.
public void DoAcceptTcpClientCallback(IAsyncResult ar)
{
// Get the listener that handles the client request.
TcpListener listener = (TcpListener)ar.AsyncState;
// End the operation and display the received data on the console.
Console.WriteLine("Client connection completed");
Clients.Add(listener.EndAcceptTcpClient(ar));
// Signal the calling thread to continue.
TcpClientConnected.Set();
}
WebAPI Controller:
public class ValuesController : ApiController
{
// GET api/values/5
public string Get(int id)
{
return $"Foo: {id}";
}
}
As mentioned earlier, what I seek is "communication" between the WebAPI and the windows service. How can I pass the "id" parameter from the WebAPI call to the _engineService object in my windows service? Perhaps something similar to WPF's MVVM Light Messenger? The idea is that it would then be parsed and sent to the appropriate TcpClient that is stored in the Clients List.
Any advice on how to achieve this will be appreciated. Please feel free to ask for clarification/more code.
Did you find any answer to your issue yet ?
I don't quite understand what you try to achieve looking for a communication between the two of them ? Do you want to somehow rely on TCP/IP to relay this id or in-memory ?
Potentially, you could consider a Mediator pattern and use this kind of library that seems quite useful in the case I understood : https://github.com/jbogard/MediatR
In a simpler approach, I would rely on events to achieve what you are trying to do, which is having a reactive communication from the HTTP request to the c++ users.
Did I understand you needs ? I am quite curious about the solution
I'm assuming you are trying to take an HTTP GET request's ID parameter and send it to TCP clients who are connected to the EngineService. If your EngineService is initialized before your ApiService, I think this is a question of how to get a handle to the one-and-only EngineService instance from within an ApiService's controller instances.
If I'm following you, you could make the EngineService a public static property of your ContainerService and reference it as ContainerService.EngineService from the controller (or anywhere in the app for that matter) or better register your EngineService as a singleton in a DI container an inject it into the ApiService.
Solution (calls to WebAPI trigger EngineService)
I now use RabbitMQ/EasyNetQ to achieve communication between the WebApi and the EngineService object containing my TCP clients.
I have incidentally split them into two separate Projects/Topshelf services now.
The following is the new "communication" component and it is instantiated in the EngineService constructor.
public class Communication
{
private readonly Logger _logger;
private readonly IBus _bus;
public delegate void ReceivedEventHandler(string data);
public event ReceivedEventHandler Received;
protected virtual void OnReceive(string data)
{
Received?.Invoke(data);
}
public Communication()
{
_logger = new Logger();
_bus = RabbitHutch.CreateBus("host=localhost", reg => reg.Register<IEasyNetQLogger>(log => _logger));
SubscribeAllQueues();
}
private void SubscribeAllQueues()
{
_bus.Receive<Message>("pipeline", message =>
{
OnReceive(message.Body);
});
}
public void SubscribeQueue(string queueName)
{
_bus.Receive<Message>(queueName, message =>
{
OnReceive(message.Body);
});
}
}
An event handler is then added.
This means that as soon as a message arrives to the bus, the data will be relayed to the event handler which will subsequently relay it to the first connected TCP client in the list.
public void Handler(string data)
{
//Console.WriteLine(data);
_clients[0].Client.Send(Encoding.UTF8.GetBytes(data));
}
...
_comPipe.Received += Handler;
And finally on the WebApi's controller:
public string Get(int id)
{
ServiceCom.SendMessage("ID: " + id);
return "value";
}
ServiceCom class. Allows sending a string message on the bus.
public static class ServiceCom
{
public static void SendMessage(string messageBody)
{
var messageBus = RabbitHutch.CreateBus("host=localhost");
messageBus.Send("pipeline", new Message { Body = messageBody });
}
}
Now that this is done, I am now looking to implement a way for the connected TCP clients to trigger updates/events in an additional SPA project that will act as a Portal / Client Management App.
My approach will probably make use of KnockOut.js and SignalR to achieve dynamic Views where TCP client events are displayed immediately and similarly actions on to WebAPI will trigger events in the TCP clients. I know it sounds like a bizarre combination of processes but it is all according to plan and working out as expected :)

Ping replies of a WCF service while busy with long running operations

I'm developing a client/server application using WPF and WCF.
The server application hosts a WCF service that is in charge to execute clients requests and callback them when something occurs.
The service interface define a duplex callback contract with all OneWay operations.
(simplified) IService
[ServiceContract(CallbackContract = typeof(ISrvServiceCallback))]
public interface ISrvService
{
[OperationContract(IsOneWay = true)]
void Ping();
[OperationContract(IsOneWay = true)]
void LongRunningOperation();
}
public interface ISrvServiceCallback
{
[OperationContract(IsOneWay = true)]
void PingReply();
[OperationContract(IsOneWay = true)]
void LongRunningOperationStatus(string reply);
}
The service needs to mantain some objects that change states according to clients calls. For this reason I decided to have a singleton service.
(simplified) Service
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class SrvService : ISrvService
{
MyObject statusObject;
public void LongRunningOperation()
{
//calling back the client reporting operation status
OperationContext.Current.GetCallbackChannel<ISrvServiceCallback>()
.LongRunningOperationStatus("starting long running application");
statusObject.elaborateStatus();
//calling back the client reporting object status
OperationContext.Current.GetCallbackChannel<ISrvServiceCallback>()
.LongRunningOperationStatus("object status: " + statusObject.ToString());
}
public void Ping()
{
OperationContext.Current.GetCallbackChannel<ISrvServiceCallback>().PingReply();
}
public SrvService()
{
statusObject= ...statusObject init...
}
}
As you can see I have a Ping operation exposed by the service that a client calls (every 5 seconds) to check if the server application is available on the network (each client has a server connectivity icon with red=server not available, green=server not available).
When a client requests a long running operation, the server starts working on that operation and can't reply to the ping requests (the client's server connectivity icon turns red).
Once the long running operation finishes, the server replies to all the requests made by the client and the server connectivity icon turns back green).
I would like to find a way to develop the service so the server always replies to the ping requests, also when a long operation is running.
How can I do it considering that i need to keep
InstanceContextMode.Single to mantain the state of the objects of
the service?
Are there other/better ways to ping a WCF service
availability and visually display the result on the client?
With a singleton service you're going to need a multi threaded implementation of your server instance to get the desired behavior, at the very least you'll need to run LongRunningOperation on a separate thread. If this operation is inherently not thread safe, you'll need to guard against multiple concurrent calls to it specifically with a lock or semaphore, etc in the implementation. This way when a client calls LongRunningOperation(), it executes in a separate thread and is free to respond to ping requests.
There are many ways to implement this. By the way you worded your question the client seems to be making asynchronous calls (as it appears to be making ping requests while waiting for LongRunningOperation to return) - so I'm also going to assume you have some knowledge of asynchronous programming. WCF has some built in ways of handling concurrency, but most of the documentation does not cover singleton instances so you're going to need to read carefully and focus on that special case.
I've had the most success with the async/await pattern (see here and here) - Once this was set up properly I had a very reliable and predictable pattern for long running service calls in a stateful singleton service.
Also, as far as pings are concerned you do point out that you are simply displaying the connectivity status for the user, but if you had plans to use it for control (checking if the service is online before making a call) there is a lot of discussions here on why you should avoid it.
EDIT: Quick example with async/await
[ServiceContract]
public interface ISrvService()
{
[OperationContract]
bool Ping(); // doesnt need to be async
[OperationContract]
Task<string> LongRunningOperation();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class SrvService : ISrvService
{
MyObject statusObject;
public async Task LongRunningOperation()
{
// lock/semaphore here if needed
await Task.Run(() => statusObject.elaborateStatus()); // you could impliment elaborateStatus() as an async Task method and call it without Task.Run
return statusObject.ToString();
}
public bool Ping()
{
return true;
}
public SrvService()
{
statusObject= ...statusObject init...
}
}
public class SrvClient : ClientBase<ISrvService>
{
public async Task<string> LongRunningOperation()
{
return await base.Channel.LongRunningOperation();
}
public async Task<bool> Ping()
{
// note that we still call this with an await. In the client we are awaiting the wcf service call
// this is independent of any async/await that happens on the server
return await Task.Run(() => base.Channel.Ping());
}
}
Using the client:
public class SomeApplicationClass()
{
SrvClient Client;
DispatcherTimer PingTimer;
public SomeClass()
{
BasicHttpBinding binding = new BasicHttpBinding();
EndpointAddress endpoint = new EndpointAddress(
"http://...:8000/Service/Address");
OutpostClient = new OutpostRemoteClient(binding, endpoint);
// pingTimer setup
}
// async voids are scary, make sure you handle exceptions inside this function
public async void PingTimer_Tick()
{
try
{
await Client.Ping();
// ping succeeded, do stuff
}
catch // specify exceptions here
{
// ping failed, do stuff
}
}
public async Task DoTheLongRunningOperation()
{
// set busy variables here etc.
string response = await Client.LongRunningOperation();
// handle the response status here
}
}
Also this answer seems relevant.

Async WCF self hosted service

My objective is to implement an asynchronous self hosted WCF service which will run all requests in a single thread and make full use of the new C# 5 async features.
My server will be a Console app, in which I will setup a SingleThreadSynchronizationContext, as specified here, create and open a ServiceHost and then run the SynchronizationContext, so all the WCF requests are handled in the same thread.
The problem is that, though the server was able to successfully handle all requests in the same thread, async operations are blocking the execution and being serialized, instead of being interlaced.
I prepared a simplified sample that reproduces the issue.
Here is my service contract (the same for server and client):
[ServiceContract]
public interface IMessageService
{
[OperationContract]
Task<bool> Post(String message);
}
The service implementation is the following (it is a bit simplified, but the final implementation may access databases or even call other services in asynchronous fashion):
public class MessageService : IMessageService
{
public async Task<bool> Post(string message)
{
Console.WriteLine(string.Format("[Thread {0} start] {1}", Thread.CurrentThread.ManagedThreadId, message));
await Task.Delay(5000);
Console.WriteLine(string.Format("[Thread {0} end] {1}", Thread.CurrentThread.ManagedThreadId, message));
return true;
}
}
The service is hosted in a Console application:
static void Main(string[] args)
{
var syncCtx = new SingleThreadSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(syncCtx);
using (ServiceHost serviceHost = new ServiceHost(typeof(MessageService)))
{
NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
serviceHost.AddServiceEndpoint(typeof(IMessageService), binding, address);
serviceHost.Open();
syncCtx.Run();
serviceHost.Close();
}
}
As you can see, the first thing I do is to setup a single threaded SynchronizationContext. Following, I create, configure and open a ServiceHost. According to this article, as I've set the SynchronizationContext prior to its creation, the ServiceHost will capture it and all the client requests will be posted in the SynchronizationContext. In the sequence, I start the SingleThreadSynchronizationContext in the same thread.
I created a test client that will call the server in a fire-and-forget fashion.
static void Main(string[] args)
{
EndpointAddress ep = new EndpointAddress(address);
NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
IMessageService channel = ChannelFactory<IMessageService>.CreateChannel(binding, ep);
using (channel as IDisposable)
{
while (true)
{
string message = Console.ReadLine();
channel.Post(message);
}
}
}
When I execute the example, I get the following results:
Client
Server
The messages are sent by the client with a minimal interval ( < 1s).
I expected the server would receive the first call and run it in the SingleThreadSynchronizationContext (queueing a new WorkItem. When the await keyword was reached, the SynchronizationContext would be once again captured, the continuation posted to it, and the method would return a Task at this point, freeing the SynchronizationContext to deal with the second request (at least start dealing with it).
As you can see by the Thread's id in the server log, the requests are being correctly posted in the SynchronizationContext. However, looking at the timestamps, we can see that the first request is being completed before the second is started, what totally defeats the purpose of having a async server.
Why is that happening?
What is the correct way of implementing a WCF self hosted async server?
I think the problem is with the SingleThreadSynchronizationContext, but I can't see how to implement it in any other manner.
I researched the subject, but I could not find more useful information on asynchronous WCF service hosting, especially using the Task based pattern.
ADDITION
Here is my implementation of the SingleThreadedSinchronizationContext. It is basically the same as the one in the article:
public sealed class SingleThreadSynchronizationContext
: SynchronizationContext
{
private readonly BlockingCollection<WorkItem> queue = new BlockingCollection<WorkItem>();
public override void Post(SendOrPostCallback d, object state)
{
this.queue.Add(new WorkItem(d, state));
}
public void Complete() {
this.queue.CompleteAdding();
}
public void Run(CancellationToken cancellation = default(CancellationToken))
{
WorkItem workItem;
while (this.queue.TryTake(out workItem, Timeout.Infinite, cancellation))
workItem.Action(workItem.State);
}
}
public class WorkItem
{
public SendOrPostCallback Action { get; set; }
public object State { get; set; }
public WorkItem(SendOrPostCallback action, object state)
{
this.Action = action;
this.State = state;
}
}
You need to apply ConcurrencyMode.Multiple.
This is where the terminology gets a bit confusing, because in this case it doesn't actually mean "multi-threaded" as the MSDN docs state. It means concurrent. By default (single concurrency), WCF will delay other requests until the original operation has completed, so you need to specify multiple concurrency to permit overlapping (concurrent) requests. Your SynchronizationContext will still guarantee only a single thread will process all the requests, so it's not actually multi-threading. It's single-threaded concurrency.
On a side note, you might want to consider a different SynchronizationContext that has cleaner shutdown semantics. The SingleThreadSynchronizationContext you are currently using will "clamp shut" if you call Complete; any async methods that are in an await are just never resumed.
I have an AsyncContext type that has better support for clean shutdowns. If you install the Nito.AsyncEx NuGet package, you can use server code like this:
static SynchronizationContext syncCtx;
static ServiceHost serviceHost;
static void Main(string[] args)
{
AsyncContext.Run(() =>
{
syncCtx = SynchronizationContext.Current;
syncCtx.OperationStarted();
serviceHost = new ServiceHost(typeof(MessageService));
Console.CancelKeyPress += Console_CancelKeyPress;
var binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
serviceHost.AddServiceEndpoint(typeof(IMessageService), binding, address);
serviceHost.Open();
});
}
static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
{
if (serviceHost != null)
{
serviceHost.BeginClose(_ => syncCtx.OperationCompleted(), null);
serviceHost = null;
}
if (e.SpecialKey == ConsoleSpecialKey.ControlC)
e.Cancel = true;
}
This will translate Ctrl-C into a "soft" exit, meaning the application will continue running as long as there are client connections (or until the "close" times out). During the close, existing client connections can make new requests, but new client connections will be rejected.
Ctrl-Break is still a "hard" exit; there's nothing you can do to change that in a Console host.

Categories

Resources