Synchronously call ProcessEventsAsync to receive messages in C# - c#

I have Windows Service written in C#, which is subscribing to Event hubs and listening to them for any messages.
The code I have followed is as follows:
public class SimpleEventProcessor : IEventProcessor
{
public Task CloseAsync(PartitionContext context, CloseReason reason)
{
Console.WriteLine($"Processor Shutting Down. Partition '{context.PartitionId}', Reason: '{reason}'.");
return Task.CompletedTask;
}
public Task OpenAsync(PartitionContext context)
{
Console.WriteLine($"SimpleEventProcessor initialized. Partition: '{context.PartitionId}'");
return Task.CompletedTask;
}
public Task ProcessErrorAsync(PartitionContext context, Exception error)
{
Console.WriteLine($"Error on Partition: {context.PartitionId}, Error: {error.Message}");
return Task.CompletedTask;
}
public Task ProcessEventsAsync(PartitionContext context, IEnumerable<EventData> messages)
{
foreach (var eventData in messages)
{
var data = Encoding.UTF8.GetString(eventData.Body.Array, eventData.Body.Offset, eventData.Body.Count);
Console.WriteLine($"Message received. Partition: '{context.PartitionId}', Data: '{data}'");
DoSomethingWithMessage(); // typically takes 20-20 mins to finish this method.
}
return context.CheckpointAsync();
}
}
This is the sample code mentioned in this document.
Now I have 8 partitions on my event hub. So 8 partitions keep receiving messages as and when there is a new message. Whenever a message is received, a method is called DoSomethingWithMessage() which takes around 30 mins to finish as it does some calculations.
I want my code to run synchronously, meaning when one message is received, service should first complete this method execution, then receive the next message. Now what is happening is , even when the DoSomethingWithMessage() is still under execution, new messages are being received and that message is processed parallel to the first message, which is creating some calculation problems.
Is there a way I can receive messages one by one?

Related

How to handle updates in the right way using signalr?

I have a client application Angular and a signalR hub, and also I have a service that take a timestamp as a parameter.
I want to invoke a method in the hub when I press on a start button in the client, and when the method is invoked I want to keep listing to all the changes (create a timer) until the client press on the stop button then I will stop the timer.
So I want to ask which is better:
1- Call the invoked method from the client with time stamp and then create a setInterval to call the method in it and when the stop button is pressed I can stop it.
Pros:
It is easy to start and stop the timer.
Cons:
I am invoking the method each 1 sec, and then I am checking on the client if there are response to update the UI.
2- Invoke the method once and then create a timer for each client on the server and when the client press on stop button I can invoke another method to stop the timer for that client.
Pros:
I am checking the timestamp in the hub and I will send the data to the client only if the timeStamp from the service > timeStamp locally
Cons:
I actually don't know how to create a timer for each client, so if this is the right way please help me
You are using SignalR for real time data communication. Invoking a method every second is just joking on the SignalR face... So this is not a solution.
The best solution would be using the group feature.
Example:
You start button will add the user to an group.
While your user is on the group it will receive all the data you need. await this.Clients.Group("someGroup").BroadcastMessage(message);
Your stop button will remove the user from the group so it will not receive data anymore.
Some code example on the hub:
public async Task Start()
{
// Add user to the data group
await this.Groups.AddToGroupAsync(this.Context.ConnectionId, "dataGroup");
}
public async Task Stop()
{
// Add user to the data group
await this.Groups.RemoveFromGroupAsync(this.Context.ConnectionId, "dataGroup");
}
Worker example that sends data to the users that pressed start and receive real time data.
private readonly IHubContext<SignalRHub, ISignalRHub> hub;
private readonly IServiceProvider serviceProvider;
public Worker(IServiceProvider serviceProvider, IHubContext<SignalRHub, ISignalRHub> hub)
{
this.serviceProvider = serviceProvider;
this.hub = hub;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await this.hub.Clients.Group("dataGroup").BroadcastMessage(DataManager.GetData());
this.Logger.LogDebug("Sent data to all users at {0}", DateTime.UtcNow);
await Task.Delay(1000, stoppingToken);
}
}
PS: Where you have the worker, I assume you have some manager that gets the data or something to be sent to the user.
Edit: If you don't want to user worker, you can always just the timer like:
public class TimerManager
{
private Timer _timer;
private AutoResetEvent _autoResetEvent;
private Action _action;
public DateTime TimerStarted { get; }
public TimerManager(Action action)
{
_action = action;
_autoResetEvent = new AutoResetEvent(false);
_timer = new Timer(Execute, _autoResetEvent, 1000, 2000);
TimerStarted = DateTime.Now;
}
public void Execute(object stateInfo)
{
_action();
if((DateTime.Now - TimerStarted).Seconds > 60)
{
_timer.Dispose();
}
}
}
And then use it somewhere like:
var timerManager = new TimerManager(() => this.hub.Clients.Group("dataGroup").BroadcastMessage(DataManager.GetData()));
Option #1 isn't available since SignalR exists to remove the need for polling. Frequent polling doesn't scale either. If every client polled the server every 1 second, the web site would end up paying a lot of CPU and bandwidth for nothing. Business people don't like frequent polling either, as all hosters and cloud providers charge for egress.
The SignalR streaming examples use timed notifications as a simple example of streaming notifications using IAsyncEnumerable<T>. In the simplest example, a counter increments every delay milliseconds :
public class AsyncEnumerableHub : Hub
{
public async IAsyncEnumerable<int> Counter(
int count,
int delay,
[EnumeratorCancellation]
CancellationToken cancellationToken)
{
for (var i = 0; i < count; i++)
{
// Check the cancellation token regularly so that the server will stop
// producing items if the client disconnects.
cancellationToken.ThrowIfCancellationRequested();
yield return i;
// Use the cancellationToken in other APIs that accept cancellation
// tokens so the cancellation can flow down to them.
await Task.Delay(delay, cancellationToken);
}
}
}
The client can call this action passing the desired delay and just start receiving notifications. SignalR knows this is a stream of notifications because it returns IAsyncEnumerable.
The next, more advanced example uses Channels to allow the publisher method WriteItemsAsync to send a stream of notifications to the hub.
The action itself is simpler, it just returns the Channel's reader:
public ChannelReader<int> Counter(
int count,
int delay,
CancellationToken cancellationToken)
{
var channel = Channel.CreateUnbounded<int>();
// We don't want to await WriteItemsAsync, otherwise we'd end up waiting
// for all the items to be written before returning the channel back to
// the client.
_ = WriteItemsAsync(channel.Writer, count, delay, cancellationToken);
return channel.Reader;
}
The publisher method writes to the ChannelWriter instead of returning an IAsyncEnumerable :
private async Task WriteItemsAsync(
ChannelWriter<int> writer,
int count,
int delay,
CancellationToken cancellationToken)
{
Exception localException = null;
try
{
for (var i = 0; i < count; i++)
{
await writer.WriteAsync(i, cancellationToken);
// Use the cancellationToken in other APIs that accept cancellation
// tokens so the cancellation can flow down to them.
await Task.Delay(delay, cancellationToken);
}
}
catch (Exception ex)
{
localException = ex;
}
writer.Complete(localException);
}
This method can easily be in a different class. All that's needed is to pass ChannelWriter to the publisher.

How to execute for loop only once in asyn method which continuously get event from eventhub

I tried with break inside my async method for loop but as getting events from event hub continuously it is calling method again and again.
I want to execute loop only once when get data == "mytestdata".
If its true sending notification mail and need to break as i don't want to send again a mail because i have already send it.
I tried using isMailSend = true; too but it execute twic.
bool isMailSend = false;
async Task IEventProcessor.ProcessEventsAsync(PartitionContext context, IEnumerable<EventData> messages)
{
foreach (EventData eventData in messages)
{
string data = Encoding.UTF8.GetString(eventData.GetBytes());
if (data == "mytestdata")
{
//send mail notification
isMailSend = true;
Console.WriteLine("mail send ");
break;
}
Console.WriteLine(string.Format("Message received. Partition: '{0}', Data: '{1}'",
context.Lease.PartitionId, data));
}
//Call checkpoint every 1 minutes, so that worker can resume processing from 1 minutes back if it restarts.
if (this.checkpointStopWatch.Elapsed > TimeSpan.FromMinutes(1))
{
await context.CheckpointAsync();
this.checkpointStopWatch.Restart();
}
}
When your ProcessEventsAsync encounters the await, control is given back to its caller to see if the caller still can do something until the caller encounters the await, after which control goes up in the call stack to see if the caller has something to do, etc.
So your thread sees the await. One of your callers in the callstack is not awaiting, and calls ProcessEventsAsync. Although you have set isMailSent, you don't check it, and thus your foreach look is entered for the second time.
Change your code as follows:
class MyClass
{
private bool isMailSent = false;
async Task IEventProcessor.ProcessEventsAsync(PartitionContext context, IEnumerable<EventData> messages)
{
if (this.isMailSent)
{
// do whatever you want to do if a 2nd call is received
// while isMailSent
ProcessWhileIsMailSent(...)
}
else
{ // called for the first time
foreach (EventData eventData in messages)
{
string data = ...
if (data == "mytestdata")
{
this.isMailSent = true;
// etc.
The effect is, that while isMailSent is true, and one of your callers decides to call you, your foreach is not called, but (in this example) ProcessWhileIsMailSent(...)
Don't forget to set isMailSent to false when you are ready to process received input again.
If you are multithreading consider locking isMailSent before accessing it.

Use ReliableQueue in ServiceFabric without polling?

I've been looking at stateful services within Service Fabric. I've been digging through the examples, specifically the WordCount. They have a RunAsync method that looks like this inside of the WordCountService:
protected override async Task RunAsync(CancellationToken cancellationToken)
{
IReliableQueue<string> inputQueue = await this.StateManager.GetOrAddAsync<IReliableQueue<string>>("inputQueue");
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
try
{
using (ITransaction tx = this.StateManager.CreateTransaction())
{
ConditionalValue<string> dequeuReply = await inputQueue.TryDequeueAsync(tx);
if (dequeuReply.HasValue)
{
//... {more example code here }
}
await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken);
}
catch (TimeoutException)
{
//Service Fabric uses timeouts on collection operations to prevent deadlocks.
//If this exception is thrown, it means that this transaction was waiting the default
//amount of time (4 seconds) but was unable to acquire the lock. In this case we simply
//retry after a random backoff interval. You can also control the timeout via a parameter
//on the collection operation.
Thread.Sleep(TimeSpan.FromSeconds(new Random().Next(100, 300)));
continue;
}
catch (Exception exception)
{
//For sample code only: simply trace the exception.
ServiceEventSource.Current.MessageEvent(exception.ToString());
}
}
}
Essentially, in this example, the service is polling the ReliableQueue every 100ms for messages. Is there a way to do this without the poll? Can we subscribe to an event or something that gets triggered when a message is successfully added to the ReliableQueue?
I'd recommend using a ReliableDispatcher in your service, or just use a Dispatcher Service.
Using the Dispatcher Service allows you to write a method that is invoked whenever an item is enqueued on the underlying reliable queue.
For example:
public override async Task OnItemDispatchedAsync(
ITransaction transaction,
int value,
CancellationToken cancellationToken)
{
// Do something with the value that has been dequeued
}
Both the Reliable Dispatcher and Dispatcher Service can be used via a NuGet package and there's full documentation and samples on GitHub to get you started:
Example of using Dispatcher Service
Example of using Reliable Dispatcher
No, currently there are no events you can use for ReliableQueue. You have to poll for new items.

Azure Worker Role to process jobs Asynchronously

I am trying to implement the following Use Case. I have an Azure Worker Role that will monitor the Azure Storage Queue, and when a message comes in, this will trigger a job to run Asynchronously. I want to use the TPL if possible, and need the operations to support cancellation, so that when the Azure Role OnStop fires, jobs can exit gracefully if possible. The MyFixIt example posted by Scott Guthrie is almost exactly what I need, and I have used this as the template for my project. The one critical aspect not supported is the requirement to run the jobs asynchronously. In the FixIt code, once a job is launched, no other jobs will process until that one finishes. Some of the jobs my application will process are long running, and I need the worker role to be able to notice other incoming jobs and run those while the long running job is running.
The 2 key methods here are ProcessMessagesAsync, which monitors the queue, and ProcessMessage, which will run the job when a message comes in. Here is what I have, and it mostly works except it does not handle the CancellationRequest properly, and the Azure Worker Role will shut down without waiting for jobs to complete.
/// <summary>
/// Continuous loop that monitors the queue and launches jobs when they are retrieved.
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
public virtual async Task ProcessMessagesAsync(CancellationToken token)
{
CloudQueue queue = _queueClient.GetQueueReference(_queueName);
await queue.CreateIfNotExistsAsync(token);
while (!token.IsCancellationRequested)
{
Debug.WriteLine("inLoop");
// The default timeout is 90 seconds, so we won’t continuously poll the queue if there are no messages.
// Pass in a cancellation token, because the operation can be long-running.
CloudQueueMessage message = await queue.GetMessageAsync(token);
if (message != null)
{
ProcessMessage(message, queue, token);
}
else
{
await Task.Delay(500, token);
}
}
}
protected virtual async Task ProcessMessage(CloudQueueMessage message, CloudQueue queue, CancellationToken token)
{
var jobDetails = JobDetails.DeserializeJson(message.AsString);
var result = await _jobRunner.RunJob(jobDetails, token);
//todo handle error
//if (result.Status == JobStatus.Error)
await queue.DeleteMessageAsync(message);
}
Then the JobRunner runs the job requested. I have written a TestJob in which I am trying to simulate a long running job that can notice the CancellationRequest, and after a short cleanup period, exit the job early.
public virtual async Task<JobResult> RunJob(JobDetails jobDetails, CancellationToken token)
{
switch (jobDetails.JobName.ToLower())
{
case "testjob":
return await TestJob(jobDetails.Args, token);
}
return new JobResult(JobStatus.Error) { ErrorMessage = "The job requested does not exist." };
}
protected virtual async Task<JobResult> TestJob(List<string> jobArgs, CancellationToken token)
{
var message = "no message";
if (jobArgs != null && jobArgs.Any())
message = jobArgs[0];
return await Task.Run(async () =>
{
Debug.WriteLine(string.Format("Start:{0}", message));
for (int i = 1; i <= 800; i++)
{
if (token.IsCancellationRequested)
{
Debug.WriteLine("CancelationRequest in TestJob");
//simulate short time to cleanup and exit early
Thread.Sleep(1500);
Debug.WriteLine("Cancelation Job Cleanup finsihed.");
token.ThrowIfCancellationRequested();
}
Thread.Sleep(10);
}
Debug.WriteLine(string.Format("Finish:{0}", message));
return new JobResult(JobStatus.Success);
});
}
I have been searching and researching for 2 days now, including the TPL DataFlow library, and have not yet been able to come up with a way to make this work properly. I feel like the Call to ProcessMessage(message, queue, token) is not being done correctly, there even is a compiler warning 'Because this call is not awaited...'. But I DON'T want to await (which is what the FixIt example does), because then no other jobs get noticed until the running one is finished. This seems like it would not be an uncommon use case, though I cannot seem to find anyone describing it.
Thank you in advance for any help!
Danny Green
The reason this is happening is because you are not honouring the task returned from ProcessMessage. Because of this ProcessMessageAsync can finish before ProcessMessage gracefully completes or cancels. Keeping in mind that you don't want to await ProcessMessage because it will make message processing sequential, I would suggest that you keep a list of running tasks.
In other words, create a List in ProcessMessageAsync and add the task returned from ProcessMessage to this list. Then at the end of while loop you should loop through this list to cancel all pending tasks if token was cancelled.
Sorry I don't have VS handy but I hope you get the point.
Thank you Sanjay, Based on your suggestion I have come up with the following.
/// <summary>
/// Continuous loop that monitors the queue and launches jobs when they are retrieved.
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
public virtual async Task ProcessMessagesAsync(CancellationToken token)
{
CloudQueue queue = _queueClient.GetQueueReference(_queueName);
await queue.CreateIfNotExistsAsync(token);
var runningTasks = new ConcurrentDictionary<int, Task>();
while (!token.IsCancellationRequested)
{
Debug.WriteLine("inLoop");
// The default timeout is 90 seconds, so we won’t continuously poll the queue if there are no messages.
// Pass in a cancellation token, because the operation can be long-running.
CloudQueueMessage message = await queue.GetMessageAsync(token);
if (message != null)
{
var t = ProcessMessage(message, queue, token);
var c = t.ContinueWith(z => RemoveRunningTask(t.Id, runningTasks));
while (true)
{
if (runningTasks.TryAdd(t.Id, t))
break;
Task.Delay(25);
}
}
else
{
try
{
await Task.Delay(500, token);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
}
while (!runningTasks.IsEmpty)
{
Debug.WriteLine("Waiting for running tasks");
Task.Delay(500);
}
}
private static void RemoveRunningTask(int id, ConcurrentDictionary<int, Task> runningTasks)
{
while (true)
{
Task outTask;
if (runningTasks.TryRemove(id, out outTask))
break;
Task.Delay(25);
}
}
This seems to work, though I feel it is a little clumsy. I started out coding the 'ContinueWith' like this, but was surprised that the incoming task had a different Id value (I expected it to be the same Task):
var task = ProcessMessage(message, queue, token).ContinueWith(x =>
{
while (true)
{
Task outTask;
if (runningTasks.TryRemove(x.Id, out outTask))
break;
Task.Delay(25);
}
});
UPDATE:
It turns out that this still does not quite work, I somehow misread the results when testing earlier. Based on the MyFixIt example, in the Work Role OnStop I have the following code:
public override void OnStop()
{
Debug.WriteLine("OnStop_Begin");
tokenSource.Cancel();
tokenSource.Token.WaitHandle.WaitOne();
base.OnStop();
Debug.WriteLine("Onstop_End");
tokenSource.Dispose();
}
It appears that the tokenSource.Token.WaitHandle.WaitOne isn't really able to wait until all of the tasks that have a reference to the token have finished, so the role continues and stops even when tasks are still in the processing of finishing up. Is there some way to properly use the token to signal when the cancellation is actually completed?
Thanks!
UPDATE 2
Ok, I think I have a solution that is now working. It appears that the CancellationToken.WaitHandle is signaled when the .Cancel is called, so I'm not sure what the purpose of having it immediately after the .Cancel is called, it seems like it would always just continue immediately through that code? This is how it is in the FixIt example, but I don't really understand it. For my purpose, I have changed ProcessMessagesAsync to now get passed in a ManualResetEventSlim, and then set that after all tasks have finished. Then in OnStop I wait on that before finishing the Stop.
/// <summary>
/// Continuous loop that monitors the queue and launches jobs when they are retrieved.
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
public virtual async Task ProcessMessagesAsync(CancellationToken token, ManualResetEventSlim reset)
{
CloudQueue queue = _queueClient.GetQueueReference(_queueName);
await queue.CreateIfNotExistsAsync(token);
var runningTasks = new ConcurrentDictionary<int, Task>();
while (!token.IsCancellationRequested)
{
Debug.WriteLine("inLoop");
// The default timeout is 90 seconds, so we won’t continuously poll the queue if there are no messages.
// Pass in a cancellation token, because the operation can be long-running.
CloudQueueMessage message = await queue.GetMessageAsync(token);
if (message != null)
{
var t = ProcessMessage(message, queue, token);
var c = t.ContinueWith(z => RemoveRunningTask(t.Id, runningTasks));
while (true)
{
if (runningTasks.TryAdd(t.Id, t))
break;
await Task.Delay(25);
}
}
else
{
try
{
await Task.Delay(500, token);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
}
while (!runningTasks.IsEmpty)
{
Debug.WriteLine("Waiting for running tasks");
await Task.Delay(500);
}
Debug.WriteLine("All tasks have finished, exiting ProcessMessagesAsync.");
reset.Set();
}
public override void OnStop()
{
Debug.WriteLine("OnStop_Begin");
tokenSource.Cancel();
tokenSource.Token.WaitHandle.WaitOne();
_reset.Wait();
base.OnStop();
Debug.WriteLine("Onstop_End");
tokenSource.Dispose();
}

Get batches of messages as available

I am trying to achieve the following behaviour using the Task Parallel Library:
As messages arrive I would like to process them sequentially but in groups. So when the first message arrives it should be processed immediately. If 2 messages come in while the first is being processed then they should be processed in a group of 2.
I can almost get what I want using a BatchBlock linked to an ActionBlock
var batchBlock = new BatchBlock<int>(100);
var actionBlock = new ActionBlock<int[]>(list =>
{
// do work
// now trigger
batchBlock.TriggerBatch();
});
batchBlock.LinkTo(actionBlock);
The problem with the code above is that if an item arrives after the TriggerBatch() call then it needs to wait for the batch to fill up. If I trigger batch after each post instead then the ActionBlock always receives single messages.
Instead of BatchBlock, you could use BufferBlock with a Task the receives items from it and resends them in batches to the target, according to your logic. Because you need to try to send a message containing a batch, and cancel it if another item comes in, the target block (actionBlock in your sample) has to have BoundedCapacity set to 1.
So, what you do is that you first receive something. When you have that, you start sending asynchronously and you also try to receive more items. If sending completes first, you start over. If receiving completes first, you cancel sending, add the received items to the batch, and then start both asynchronous actions again.
The actual code is a bit more complicated, because it needs to deal with some corner cases (receiving and sending complete at the same time; sending couldn't be canceled; receiving completed, because the whole was completed; exceptions):
public static ITargetBlock<T> CreateBatchingWrapper<T>(
ITargetBlock<IReadOnlyList<T>> target)
{
// target should have BoundedCapacity == 1,
// but there is no way to check for that
var source = new BufferBlock<T>();
Task.Run(() => BatchItems(source, target));
return source;
}
private static async Task BatchItems<T>(
IReceivableSourceBlock<T> source, ITargetBlock<IReadOnlyList<T>> target)
{
try
{
while (true)
{
var messages = new List<T>();
// wait for first message in batch
if (!await source.OutputAvailableAsync())
{
// source was completed, complete target and return
target.Complete();
return;
}
// receive all there is right now
source.ReceiveAllInto(messages);
// try sending what we've got
var sendCancellation = new CancellationTokenSource();
var sendTask = target.SendAsync(messages, sendCancellation.Token);
var outputAvailableTask = source.OutputAvailableAsync();
while (true)
{
await Task.WhenAny(sendTask, outputAvailableTask);
// got another message, try cancelling send
if (outputAvailableTask.IsCompleted
&& outputAvailableTask.Result)
{
sendCancellation.Cancel();
// cancellation wasn't successful
// and the message was received, start another batch
if (!await sendTask.EnsureCancelled() && sendTask.Result)
break;
// send was cancelled, receive messages
source.ReceiveAllInto(messages);
// restart both Tasks
sendCancellation = new CancellationTokenSource();
sendTask = target.SendAsync(
messages, sendCancellation.Token);
outputAvailableTask = source.OutputAvailableAsync();
}
else
{
// we get here in three situations:
// 1. send was completed succesfully
// 2. send failed
// 3. input has completed
// in cases 2 and 3, this await is necessary
// in case 1, it's harmless
await sendTask;
break;
}
}
}
}
catch (Exception e)
{
source.Fault(e);
target.Fault(e);
}
}
/// <summary>
/// Returns a Task that completes when the given Task completes.
/// The Result is true if the Task was cancelled,
/// and false if it completed successfully.
/// If the Task was faulted, the returned Task is faulted too.
/// </summary>
public static Task<bool> EnsureCancelled(this Task task)
{
return task.ContinueWith(t =>
{
if (t.IsCanceled)
return true;
if (t.IsFaulted)
{
// rethrow the exception
ExceptionDispatchInfo.Capture(task.Exception.InnerException)
.Throw();
}
// completed successfully
return false;
});
}
public static void ReceiveAllInto<T>(
this IReceivableSourceBlock<T> source, List<T> targetCollection)
{
// TryReceiveAll would be best suited for this, except it's bugged
// (see http://connect.microsoft.com/VisualStudio/feedback/details/785185)
T item;
while (source.TryReceive(out item))
targetCollection.Add(item);
}
You can also use Timer; which will Trigger Batch on every 10 seconds

Categories

Resources