1

I am using ManualResetEventSlim to have signaling mechanism in my application and It works great if requests/sec are 100. As I increase request/sec, it gets worse.

Example:

100 Requests/sec -> 90% transaction done in 250 ms and Throughput (Success request/sec) is 134.

150 Requests/sec -> 90% transaction done in 34067 ms and Throughput (Success request/sec) is 2.2.

I use ConcurrentDictionary as give below:

// <key, (responseString,ManualResetEventSlim) >
private static ConcurrentDictionary<string, (string, ManualResetEventSlim)> EventsDict = new ConcurrentDictionary<string, (string, ManualResetEventSlim)>();

Below given process describes need for ManualResetEventSlim (Api Solution 1 and Api Solution 2 are completely :

  1. Api Solution 1 (REST Api) received a request, it added an element (null, ManualResetEventSlim) in ConcurrentDictionary against a key and called thirdparty service (SOAP) using async/await. Thirdparty soap api returned acknowledgement response but actual response is pending. After getting acknowledgement response, it goes to ManualResetEventSlim.wait

  2. Once thirdparty processed the request, it calls Api Solution 2 (SOAP) using exposed method and sends actual response. Api solution 2 sends response to Api Solution 1 (REST Api) by making http request and then inserts data to database for auditlog.

  3. Api Solution 1 will get key from response string and update response string in ConcurrentDictionary and set signal.

  4. Api Solution 1 disposes ManualResetEventSlim object before returning response to client.

Muqadar Ali
  • 87
  • 11
  • 1
    You are blocking Solution1's threads for as long as Solution2 is working. Have you considered using Task Based Async ("async/await") ? – Fildor Apr 28 '20 at 11:09
  • Yes it is a soap service and I use async/await but it always returns two responses. Solution2 is a third party service that immediately returns acknowledgement response for each request and It demands to send actual response on a separate exposed method. I need to block the request until I get actual response – Muqadar Ali Apr 28 '20 at 11:15
  • 2
    "You can use ManualResetEventSlim for better performance than ManualResetEvent when wait times are **expected to be very short**, and when the event **does not cross a process boundary**" - https://learn.microsoft.com/en-us/dotnet/api/system.threading.manualreseteventslim?view=netcore-3.1#remarks – Peter Csala Apr 28 '20 at 11:18
  • Can you elabortate a bit on how signalling from Solution 2 is working? How do you get notified when the result can be fetched? Is that a callback? An event? Something else? – Fildor Apr 28 '20 at 11:19
  • @PeterCsala I am already using ManualResetEventSlim – Muqadar Ali Apr 28 '20 at 11:19
  • Yes, Peter emphazised the part where it says "when wait times are expected to be very short" - which does not hold. And you are crossing process boundaries, too. – Fildor Apr 28 '20 at 11:20
  • @Fildor as soon as solution2 gets response from thirdparty (pre-defined contract), it makes http call to solution1 and submits the actual response returned from thirdparty. Solution1 then parses, finds event from dictionary and sets signal – Muqadar Ali Apr 28 '20 at 11:20
  • OK, I see. Maybe you should update the question to reflect this design a little bit better. – Fildor Apr 28 '20 at 11:21
  • I guess, you are not allowed to change Solution 1's API? Usually, if I expect long running computations, I'd have an endpoint for request which kicks off computation and returns a URI that is pointing to another endpoint, where status and - if ready - the result can be polled. – Fildor Apr 28 '20 at 11:27
  • @Fildor I have updated the question. Yes I cannot make much changes in Solution1. App sends request to Solution0 and Solution0 pass it to Solution1 to get it done and do whatever needs to be done. Solution0 accepts other requests in meanwhile. I have not mentioned Solution0 b/c it is not making any issue. Bottle neck is Solution1 – Muqadar Ali Apr 28 '20 at 11:33
  • I just had an idea: [TaskCompletionSource](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcompletionsource-1?view=netcore-3.1) ... I think you can do it non-blocking, if you store a TaskCompletionSource instead of the ResetEvent, await the Sources Task and then have the "callback" complete the Task. – Fildor Apr 28 '20 at 13:08

1 Answers1

2

I think, you should be able to get rid of the blocking code by replacing (string, ManualResetEventSlim) with TaskCompletionSource<string>:

In Solution 1, you would do something along this:

TaskCompletionSource<string> tcs = new TaskCompletionSource<string>()
EventsDict.AddOrUpdate( key, tcs );
await KickOffSolution2ThirdParty( /*...*/ );
string result = await tcs.Task; // <-- now not blocking any thread anymore

And the counterpart:

void CallbackFromSolution2( string key, string result )
{
     if( EventsDict.TryRemove(key, out TaskCompletionSource<string> tcs )
     {
         tcs.SetResult(result);
     }
}

This is of course only a coarse outline of the idea. But hopefully enough to make my line of thought understandable. I cannot test this right now, so any improvements/corrections welcome.

Fildor
  • 14,510
  • 4
  • 35
  • 67