Query parameters are persisting values - c#

I have to simulate a real-time data flow from the server, for that I have implemented a Timer class from the System.Threading namespace.
public class DataManager
{
private Timer _timer;
private AutoResetEvent _autoResetEvent;
private Action _action;
public DateTime TimerStarted { get; }
public DataManager(Action action)
{
_action = action;
_autoResetEvent = new AutoResetEvent(false);
_timer = new Timer(Execute, _autoResetEvent, 0, 10000);
TimerStarted = DateTime.Now;
}
public void Execute(object stateInfo)
{
_action();
if ((DateTime.Now - TimerStarted).Seconds > 60)
{
_timer.Dispose();
}
}
}
And using this class to execute repository method to get latest data from controller.
[HttpGet]
public async Task<IActionResult> Get([FromQuery] PagedTransactionDataRequest queryParams)
{
var pageSize = queryParams.PageSize ?? 1;
var pageNumber = queryParams.PageNumber ?? 10;
<b>var timerManager = new DataManager(async () =>
await _paymentDraftHub.Clients.All.SendAsync(SignalRConstants.TransferPaymentDraftServiceData, await _paymentTransactionRepository.GetAllDeclinedAsync(pageSize, pageNumber))
);</b>
var response = new ResponseMessage { Message = "Accepted", Code = "201" };
return Ok(response);
}
PROBLEM: First request to the API is working perfectly fine but during the second request pageSize and pageNumber have the old values too. When the timer runs every 10 seconds, I can see the old query parameter values and new query parameter values both are getting executed.
I am not sure why the old values are even getting executed.

When you call your first GET, you create a DataManager. That DataManager only knows about the first query params. It then executes on a timer with those parameters.
When you call your second GET, you are creating a second DataManager. That second manager will also start executing on a timer with the 2nd set of parameters, but it does not affect the first one at all.
One solution is to make the DataManager a member of your controller, and expose the desired action as a property:
private DataManager _manager;
[HttpGet]
public async Task<IActionResult> Get([FromQuery] PagedTransactionDataRequest queryParams)
{
var pageSize = queryParams.PageSize ?? 1;
var pageNumber = queryParams.PageNumber ?? 10;
if(_manager == null) {_manager = new DataManager(async () =>
await _paymentDraftHub.Clients.All.SendAsync(SignalRConstants.TransferPaymentDraftServiceData, await _paymentTransactionRepository.GetAllDeclinedAsync(pageSize, pageNumber))
);
}
else
{_manager.action = async () => await _paymentDraftHub.Clients.All.SendAsync(SignalRConstants.TransferPaymentDraftServiceData, await _paymentTransactionRepository.GetAllDeclinedAsync(pageSize, pageNumber));
}
var response = new ResponseMessage { Message = "Accepted", Code = "201" };
return Ok(response);
}
I didn't have time to test this solution, so it may not even compile, but it does give you an idea.

Related

Async await method gets stuck

I have an API method with this signature:
public Task<MResponse> StartAsync(MRequest request)
I can't change that (I can change the implementation inside but not the signature of the method).
This is how I call this method:
var ret = await _SyncClient.StartAsync(Request);
This is the implementation
public Task<MResponse> StartAsync(MRequest request)
{
if (request.Number == 1)
{
return new Task<MResponse>(() => new MResponse() { Description = "Error", Code = 1 });
}
else
{
return new Task<MResponse>(() => new MResponse() { Description = "", Code = 0 });
}
}
On the debug mode I see that my code get into StartAsync and and the return and finish it, but its look like the wait never ends....
I have no idea what it can be.
If you need more information about my code, let me know.
It looks like you're not actually having anything asynchronous to do in the implementation. That's fine, there's always cases where the signature is set (i.e. an interface) and some implementations don't need it.
You can still make the method async and just return the response as if it were as synchronous method, like so:
public async Task<MResponse> StartAsync(MRequest request)
{
return new MResponse() { ... }
}
But that will probably trigger Intellisense to tell you there's nothing being awaited in your method. A better approach is to use Task.FromResult like so:
public Task<MResponse> StartAsync(MRequest request)
{
return Task.FromResult(new MResponse() ... );
}
which will return a Task that already as completed and therefore will not block your await in the follow-up.
You are creating a task by calling the Task Constructor
This creates a Task, but does not start it. You would have to explicitly call Task.Start
public Task<MResponse> StartAsync(MRequest request)
{
var task = (request.Number == 1)
? new Task<MResponse>(() => new MResponse() { Description = "Error", Code = 1 })
: new Task<MResponse>(() => new MResponse() { Description = "", Code = 0 });
task.Start();
}
You have other options here. You can create the object on the current thread and return it as a completed task
return Task.FromResult<MResponse>(new MResponse(...))
Another option is to invoke Task.Run to kick off the work on a background thread.
public Task<MResponse> StartAsync(MRequest request)
{
Func<MResponse> generateResponse = (request.Number == 1)
? () => new MResponse() { Description = "Error", Code = 1 })
: () => new MResponse() { Description = "", Code = 0 });
return Task.Run(generateResponse);
}

How to have only one thread for fire and forget task in asp.net webapi?

I have a need in my asp.net webapi (framework .Net 4.7.2) to call Redis (using StackExchange.Redis) in order to delete a key in a fire and forget way and I am making some stress test.
As I am comparing the various way to have the max speed :
I have already test executing the command with the FireAndForget flag,
I have also measured a simple command to Redis by await it.
And I am now searching a way to collect a list of commands received in a window of 15ms and execute them all in one go by pipeling them.
I have first try to use a Task.Run Action to call Redis but the problem that I am observing is that under stress, the memory of my webapi keep climbing.
The memory is full of System.Threading.IThreadPoolWorkItem[] objects with the folowing code :
[HttpPost]
[Route("api/values/testpostfireforget")]
public ApiResult<int> DeleteFromBasketId([FromBody] int basketId)
{
var response = new DeleteFromBasketResponse<int>();
var cpt = Interlocked.Increment(ref counter);
Task.Run(async () => {
await db.StringSetAsync($"BASKET_TO_DELETE_{cpt}",cpt.ToString())
.ConfigureAwait(false);
});
return response;
}
So I think that under stress my api keep enqueing background task in memory and execute them one after the other as fast as it can but less than the request coming in...
So I am searching for a way to have only one long lived background thread running with the asp.net webapi, that could capture the commands to send to Redis and execute them by pipeling them.
I was thinking in runnning a background task by implementing IHostedService interface, but it seems that in this case the background task would not share any state with my current http request. So implementing a IhostedService would be handy for a scheduled background task but not in my case, or I do not know how...
Based on StackExchange.Redis documentation you can use CommandFlags.FireAndForget flag:
[HttpPost]
[Route("api/values/testpostfireforget")]
public ApiResult<int> DeleteFromBasketId([FromBody] int basketId)
{
var response = new DeleteFromBasketResponse<int>();
var cpt = Interlocked.Increment(ref counter);
db.StringSet($"BASKET_TO_DELETE_{cpt}", cpt.ToString(), flags: CommandFlags.FireAndForget);
return response;
}
Edit 1: another solution based on comment
You can use pub/sub approach. Something like this should work:
public class MessageBatcher
{
private readonly IDatabase target;
private readonly BlockingCollection<Action<IDatabaseAsync>> tasks = new();
private Task worker;
public MessageBatcher(IDatabase target) => this.target = target;
public void AddMessage(Action<IDatabaseAsync> task) => tasks.Add(task);
public IDisposable Start(int batchSize)
{
var cancellationTokenSource = new CancellationTokenSource();
worker = Task.Factory.StartNew(state =>
{
var count = 0;
var tokenSource = (CancellationTokenSource) state;
var box = new StrongBox<IBatch>(target.CreateBatch());
tokenSource.Token.Register(b => ((StrongBox<IBatch>)b).Value.Execute(), box);
foreach (var task in tasks.GetConsumingEnumerable(tokenSource.Token))
{
var batch = box.Value;
task(batch);
if (++count == batchSize)
{
batch.Execute();
box.Value = target.CreateBatch();
count = 0;
}
}
}, cancellationTokenSource, cancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Current);
return new Disposer(worker, cancellationTokenSource);
}
private class Disposer : IDisposable
{
private readonly Task worker;
private readonly CancellationTokenSource tokenSource;
public Disposer(Task worker, CancellationTokenSource tokenSource) => (this.worker, this.tokenSource) = (worker, tokenSource);
public void Dispose()
{
tokenSource.Cancel();
worker.Wait();
tokenSource.Dispose();
}
}
}
Usage:
private readonly MessageBatcher batcher;
ctor(MessageBatcher batcher) // ensure that passed `handler` is singleton and already already started
{
this.batcher= batcher;
}
[HttpPost]
[Route("api/values/testpostfireforget")]
public ApiResult<int> DeleteFromBasketId([FromBody] int basketId)
{
var response = new DeleteFromBasketResponse<int>();
var cpt = Interlocked.Increment(ref counter);
batcher.AddMessage(db => db.StringSetAsync($"BASKET_TO_DELETE_{cpt}", cpt.ToString(), flags: CommandFlags.FireAndForget));
return response;
}

how to detect bot Idleness on Bot Framework

I'm using bot framework V3 with C#.
I need to identify when my bot is idle for more than 5 minutes.
I've tried to handle bot idleness via the MessageController but my attempt seems not to work out.
switch (activity.Type)
{
case ActivityTypes.Message:
await Task.Delay(5000).ContinueWith(async (t) =>
{
var reply = activity.CreateReply();
var myMessage = "Bot time out. Bye";
reply.Text = myMessage;
await connector.Conversations.ReplyToActivityAsync(reply);
});
await Task.Factory.StartNew(() => Conversation.SendAsync(activity, () => new Dialogs.RootDialog(luisService).DefaultIfException()));
}
break;
}
What could be wrong?
Any sample could you could share please?
Thx in advance!
First, you're only delaying for 5 seconds (5000 miliseconds) and not 5 minutes.
Anyhow, you can try the following approach. Add this class:
public static class TimeoutConversations
{
const int TimeoutLength = 10;
private static Timer _timer;
private static TimeSpan _timeoutLength;
static TimeoutConversations()
{
_timeoutLength = TimeSpan.FromSeconds(TimeoutLength);
_timer = new Timer(CheckConversations, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
}
static ConcurrentDictionary<string, UserInfo> Conversations = new ConcurrentDictionary<string, UserInfo>();
static async void CheckConversations(object state)
{
foreach (var userInfo in Conversations.Values)
{
if (DateTime.UtcNow - userInfo.LastMessageReceived >= _timeoutLength)
{
UserInfo removeUserInfo = null;
Conversations.TryRemove(userInfo.ConversationReference.User.Id, out removeUserInfo);
var activity = userInfo.ConversationReference.GetPostToBotMessage();
//clear the dialog stack and conversation state for this user
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, activity))
{
var botData = scope.Resolve<IBotData>();
await botData.LoadAsync(CancellationToken.None);
var stack = scope.Resolve<IDialogStack>();
stack.Reset();
//botData.UserData.Clear();
botData.ConversationData.Clear();
botData.PrivateConversationData.Clear();
await botData.FlushAsync(CancellationToken.None);
}
MicrosoftAppCredentials.TrustServiceUrl(activity.ServiceUrl);
var connectorClient = new ConnectorClient(new Uri(activity.ServiceUrl), ConfigurationManager.AppSettings["MicrosoftAppId"], ConfigurationManager.AppSettings["MicrosoftAppPassword"]);
var reply = activity.CreateReply("I haven't heard from you in awhile. Let me know when you want to talk.");
connectorClient.Conversations.SendToConversation(reply);
//await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
}
}
}
public static void MessageReceived(Activity activity)
{
UserInfo userInfo = null;
if (Conversations.TryGetValue(activity.From.Id, out userInfo))
{
userInfo.LastMessageReceived = DateTime.UtcNow;
}
else
{
Conversations.TryAdd(activity.From.Id, new UserInfo()
{
ConversationReference = activity.ToConversationReference(),
LastMessageReceived = DateTime.UtcNow
});
}
}
}
public class UserInfo
{
public ConversationReference ConversationReference { get; set; }
public DateTime LastMessageReceived { get; set; }
}
And then in messages controller, call:
TimeoutConversations.MessageReceived(activity);
In this example, it is doing a 10 second timeout, checking every 5 seconds. This is a basic (somewhat sloppy) timer to time out conversations. You'll probably run into bugs, but you can tweak it until it fits your needs. Using an azure queue or something might be better.
Here is a DCR for v4 to implement this basic functionality:
https://github.com/microsoft/botframework-sdk/issues/5237

Possible HttpClient deadlock

I have a decorator class that adds the ability to rate limit http requests for hitting an API:
public class RateLimitedHttpClient : IHttpClient
{
public RateLimitedHttpClient(System.Net.Http.HttpClient client)
{
_client = client;
_client.Timeout = TimeSpan.FromMinutes(30);
//ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
}
public async Task<string> ReadAsync(string url)
{
if (!_sw.IsRunning)
_sw.Start();
await Delay();
using var response = await _client.GetAsync(url);
return await response.Content.ReadAsStringAsync();
}
private async Task Delay()
{
var totalElapsed = GetTimeElapsedSinceLastRequest();
while (totalElapsed < MinTimeBetweenRequests)
{
await Task.Delay(MinTimeBetweenRequests - totalElapsed);
totalElapsed = GetTimeElapsedSinceLastRequest();
};
_timeElapsedOfLastHttpRequest = (int)_sw.Elapsed.TotalMilliseconds;
}
private int GetTimeElapsedSinceLastRequest()
{
return (int)_sw.Elapsed.TotalMilliseconds - _timeElapsedOfLastHttpRequest;
}
private readonly System.Net.Http.HttpClient _client;
private readonly Stopwatch _sw = new Stopwatch();
private int _timeElapsedOfLastHttpRequest;
private const int MinTimeBetweenRequests = 100;
}
However, I'm noticing that at the line indicated below I get a message in the debugger that says that the next statement will execute when the current thread returns.
var epsDataPoints = await _downloader.GetEPSData(cik);
foreach (var eps in epsDataPoints)
{
// getting VS2019 debugger message here!
// assuming that the line above is deadlocking....
Console.WriteLine($"{cik} :: {eps.DateInterval} :: {eps.EPS}");
}
When I open up the Task Manager, the network bandwidth goes to 0 and everything stops with the application other than it sitting at the Console.WriteLine above.
The EPSDownloader class that uses the IHttpClient is below:
public class EPSDownloader
{
public EPSDownloader(IHttpClient client)
{
_client = client;
}
public async Task<IEnumerable<EPSDataPoint>> GetEPSData(int cik)
{
var epsDataPoints = new Dictionary<LocalDate, EPSDataPoint>();
var reportLinks = await GetReportLinks(cik);
foreach (var reportLink in reportLinks)
{
var xbrlLink = await GetXBRLLink(reportLink);
var epsData = await GetEPSData(xbrlLink);
foreach (var eps in epsData)
{
if (!epsDataPoints.ContainsKey(eps.DateInterval.End))
epsDataPoints.Add(eps.DateInterval.End, eps);
}
}
var list = epsDataPoints.OrderBy(d => d.Key).Select(e => e.Value).ToList();
return list;
}
private async Task<IList<string>> GetReportLinks(int cik)
{
// move this url elsewhere
var url = "https://www.sec.gov/cgi-bin/browse-edgar?action=getcompany&CIK=" + cik +
"&type=10&dateb=&owner=include&count=100";
var srBody = await _client.ReadAsync(url); // consider moving this to srPage
var srPage = new SearchResultsPage(srBody);
return srPage.GetAllReportLinks();
}
private async Task<string> GetXBRLLink(string link)
{
var url = SEC_HOSTNAME + link;
var fdBody = await _client.ReadAsync(url);
var fdPage = new FilingDetailsPage(fdBody);
return fdPage.GetInstanceDocumentLink();
}
private async Task<IList<EPSDataPoint>> GetEPSData(string xbrlLink)
{
var xbrlBody = await _client.ReadAsync(SEC_HOSTNAME + xbrlLink);
var xbrlDoc = new XBRLDocument(xbrlBody);
return xbrlDoc.GetAllQuarterlyEPSData();
}
private readonly IHttpClient _client;
private const string SEC_HOSTNAME = "https://www.sec.gov";
}
It seems to be that there is an issue with HttpClient, but I don't know why. No exceptions are being thrown, but I do occasionally see that threads have exited with code 0.
Update: I actually restarted my computer while the application was running and it began running fine again for about 20 minutes before the Task Manager showed 0 for the network speed and the application just sat there.

Create delay between two message reads of a Queue?

I am using Azure Queues to perform a bulk import.
I am using WebJobs to perform the process in the background.
The queue dequeues very frequently. How do I create a delay between 2 message
reads?
This is how I am adding a message to the Queue
public async Task<bool> Handle(CreateFileUploadCommand message)
{
var queueClient = _queueService.GetQueueClient(Constants.Queues.ImportQueue);
var brokeredMessage = new BrokeredMessage(JsonConvert.SerializeObject(new ProcessFileUploadMessage
{
TenantId = message.TenantId,
FileExtension = message.FileExtension,
FileName = message.Name,
DeviceId = message.DeviceId,
SessionId = message.SessionId,
UserId = message.UserId,
OutletId = message.OutletId,
CorrelationId = message.CorrelationId,
}))
{
ContentType = "application/json",
};
await queueClient.SendAsync(brokeredMessage);
return true;
}
And Below is the WebJobs Function.
public class Functions
{
private readonly IValueProvider _valueProvider;
public Functions(IValueProvider valueProvider)
{
_valueProvider = valueProvider;
}
public async Task ProcessQueueMessage([ServiceBusTrigger(Constants.Constants.Queues.ImportQueue)] BrokeredMessage message,
TextWriter logger)
{
var queueMessage = message.GetBody<string>();
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(_valueProvider.Get("ServiceBaseUri"));
var stringContent = new StringContent(queueMessage, Encoding.UTF8, "application/json");
var result = await client.PostAsync(RestfulUrls.ImportMenu.ProcessUrl, stringContent);
if (result.IsSuccessStatusCode)
{
await message.CompleteAsync();
}
else
{
await message.AbandonAsync();
}
}
}
}
As far as I know, azure webjobs sdk enable concurrent processing on a single instance(the default is 16).
If you run your webjobs, it will read 16 queue messages(peeklock and calls Complete on the message if the function finishes successfully, or calls Abandon) and create 16 processes to execute the trigger function at same time. So you feel the queue dequeues very frequently.
If you want to disable concurrent processing on a single instance.
I suggest you could set ServiceBusConfiguration's MessageOptions.MaxConcurrentCalls to 1.
More details, you could refer to below codes:
In the program.cs:
JobHostConfiguration config = new JobHostConfiguration();
ServiceBusConfiguration serviceBusConfig = new ServiceBusConfiguration();
serviceBusConfig.MessageOptions.MaxConcurrentCalls = 1;
config.UseServiceBus(serviceBusConfig);
JobHost host = new JobHost(config);
host.RunAndBlock();
If you want to create a delay between 2 message reads, I suggest you could create a custom ServiceBusConfiguration.MessagingProvider.
It contains CompleteProcessingMessageAsync method, this method completes processing of the specified message, after the job function has been invoked.
I suggest you could add thread.sleep method in CompleteProcessingMessageAsync to achieve delay read.
More detail, you could refer to below code sample:
CustomMessagingProvider.cs:
Notice: I override the CompleteProcessingMessageAsync method codes.
public class CustomMessagingProvider : MessagingProvider
{
private readonly ServiceBusConfiguration _config;
public CustomMessagingProvider(ServiceBusConfiguration config)
: base(config)
{
_config = config;
}
public override NamespaceManager CreateNamespaceManager(string connectionStringName = null)
{
// you could return your own NamespaceManager here, which would be used
// globally
return base.CreateNamespaceManager(connectionStringName);
}
public override MessagingFactory CreateMessagingFactory(string entityPath, string connectionStringName = null)
{
// you could return a customized (or new) MessagingFactory here per entity
return base.CreateMessagingFactory(entityPath, connectionStringName);
}
public override MessageProcessor CreateMessageProcessor(string entityPath)
{
// demonstrates how to plug in a custom MessageProcessor
// you could use the global MessageOptions, or use different
// options per entity
return new CustomMessageProcessor(_config.MessageOptions);
}
private class CustomMessageProcessor : MessageProcessor
{
public CustomMessageProcessor(OnMessageOptions messageOptions)
: base(messageOptions)
{
}
public override Task<bool> BeginProcessingMessageAsync(BrokeredMessage message, CancellationToken cancellationToken)
{
// intercept messages before the job function is invoked
return base.BeginProcessingMessageAsync(message, cancellationToken);
}
public override async Task CompleteProcessingMessageAsync(BrokeredMessage message, FunctionResult result, CancellationToken cancellationToken)
{
if (result.Succeeded)
{
if (!MessageOptions.AutoComplete)
{
// AutoComplete is true by default, but if set to false
// we need to complete the message
cancellationToken.ThrowIfCancellationRequested();
await message.CompleteAsync();
Console.WriteLine("Begin sleep");
//Sleep 5 seconds
Thread.Sleep(5000);
Console.WriteLine("Sleep 5 seconds");
}
}
else
{
cancellationToken.ThrowIfCancellationRequested();
await message.AbandonAsync();
}
}
}
}
Program.cs main method:
static void Main()
{
var config = new JobHostConfiguration();
if (config.IsDevelopment)
{
config.UseDevelopmentSettings();
}
var sbConfig = new ServiceBusConfiguration
{
MessageOptions = new OnMessageOptions
{
AutoComplete = false,
MaxConcurrentCalls = 1
}
};
sbConfig.MessagingProvider = new CustomMessagingProvider(sbConfig);
config.UseServiceBus(sbConfig);
var host = new JobHost(config);
// The following code ensures that the WebJob will be running continuously
host.RunAndBlock();
}
Result:

Categories

Resources