I have like that scenario:
I have an endpoint and this endpoint will save the requests in List or Queue in memory and it will return immediately success response to the consumer. This requirement is critical, the consumer should not wait for the responses, it will get responses from a different endpoint if it needs. So, this endpoint must return as quickly as possible after saving the request message in memory.
Another thread will distribute these requests to other endpoints and save the responses in memory as well.
What I did till now:
I created a controller api to save these requests in the memory. I saved them in a static request List like below:
public static class RequestList
{
public static event EventHandler<RequestEventArgs> RequestReceived;
private static List<DistributionRequest> Requests { get; set; } = new List<DistributionRequest>();
public static int RequestCount { get => RequestList.Requests.Count; }
public static DistributionRequest Add(DistributionRequest request)
{
request.RequestId = Guid.NewGuid().ToString();
RequestList.Requests.Add(request);
OnRequestReceived(new RequestEventArgs { Request = request });
return request;
}
public static bool Remove(DistributionRequest request) => Requests.Remove(request);
private static void OnRequestReceived(RequestEventArgs e)
{
RequestReceived?.Invoke(null, e);
}
}
public class RequestEventArgs : EventArgs
{
public DistributionRequest Request { get; set; }
}
And another class is subscribed to that event that exists in that static class and I am creating a new thread to make some background web requests to be able to achieve 2. item which I stated above.
private void RequestList_RequestReceived(object sender, RequestEventArgs e)
{
_logger.LogInformation($"Request Id: {e.Request.RequestId}, New request received");
Task.Factory.StartNew(() => Distribute(e.Request));
_logger.LogInformation($"Request Id: {e.Request.RequestId}, New task created for the new request");
//await Distribute(e.Request);
}
public async Task<bool> Distribute(DistributionRequest request)
{
//Some logic running here to send post request to different endpoints
//and to save results in memory
}
And here is my controller method:
[HttpPost]
public IActionResult Post([FromForm] DistributionRequest request)
{
var response = RequestList.Add(request);
return Ok(new DistributionResponse { Succeeded = true, RequestId = response.RequestId });
}
I tried that approach but it did not work as I expected, it should return within milliseconds since I am not waiting for responses but it seems to wait for something, and after every single request waiting time is increasing as below:
What am I doing wrong? Or Do you have a better idea? How can I achieve my goal?
CodePudding user response:
Based on you example code I tried to implement it without "eventing". Therefore I get much better request times. I cannot say if this is related to your implementation or the eventing itself for this you have to do profiling.
I did it this way
RequestsController
Just like you had it in your example. Take the request and add it to the requests list.
[Route("requests")]
public class RequestsController : ControllerBase
{
private readonly RequestManager _mgr;
public RequestsController(RequestManager mgr)
{
_mgr = mgr;
}
[HttpPost]
public IActionResult AddRequest([FromBody] DistributionRequest request)
{
var item = _mgr.Add(request);
return Accepted(new { Succeeded = true, RequestId = item.RequestId });
}
}
RequestManager
Manage the request list and forward them to some distribor.
public class RequestManager
{
private readonly ILogger _logger;
private readonly RequestDistributor _distributor;
public IList<DistributionRequest> Requests { get; } = new List<DistributionRequest>();
public RequestManager(RequestDistributor distributor, ILogger<RequestManager> logger)
{
_distributor = distributor;
_logger = logger;
}
public DistributionRequest Add(DistributionRequest request)
{
_logger.LogInformation($"Request Id: {request.RequestId}, New request received");
/// Just add to the list of requests
Requests.Add(request);
/// Create and start a new task to distribute the request
/// forward it to the distributor.
/// Be sure to not add "await" here
Task.Factory.StartNew(() => _distributor.DistributeAsync(request));
_logger.LogInformation($"Request Id: {request.RequestId}, New task created for the new request");
return request;
}
}
RequestDistributor
Distribution logic can be implemented here
public class RequestDistributor
{
public async Task DistributeAsync(DistributionRequest request)
{
/// do your distribution here
/// currently just a mocked time range
await Task.Delay(5);
}
}
Wire up
... add all these things to your dependency injection configuration
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSingleton<RequestDistributor>();
services.AddSingleton<RequestManager>();
}
Tests
With the here provided code pieces I received all the requests back in less than 10 ms.
Note
This is just an example try to always add interfaces to your services to make them testable ;).