3

(everything below initially written for .Net 5.0 but now targeting .Net 6.0)

Consider this Asp.Net controller used as REST Api :

[AllowAnonymous]
[Route("[controller]")]
[ApiController]
[ApiConventionType(typeof(DefaultApiConventions))]
public class DebugController : ControllerBase {

    private readonly ILoggingWrapper log;

    public DebugController(ILoggingWrapper log) {
        this.log = log;
        log.Information("Constructor called");
    }

    private async Task Slow() {
        await Task.Delay(15000).ConfigureAwait(false);
    }

    [Authorize()]
    [HttpGet]
    [Route("testSlow")]
    public async Task<ActionResult> TestSlow() {
        await Slow().ConfigureAwait(false);
        return Ok();
    }

    [Authorize()]
    [HttpGet]
    [Route("testFast")]
    public async Task<ActionResult> TestFast() {
        return Ok();
    }
}

I have a Swagger page configured, where I can call TestSlow and TestFast on demand.

Experience #1

  1. Open a browser tab with the Swagger page open,
  2. Call only TestSlow

Result

The DebugController constructor enters immediately, then TestSlow starts immediately and returns after 15 seconds

Experience #2

  1. Open a browser tab with the Swagger page open,
  2. Open another browser tab with the Swagger page open,
  3. On the first Swagger page, call only TestSlow
  4. Quickly switch to the other Swagger page, call TestFast

Result

  • The DebugController constructor enters immediately, then TestSlow starts immediately
  • When calling TestFast, and while Testslow is still running, The DebugController constructor enters immediately and TestFast starts immediately.

the two experiences above behave as I expect it : Asp.Net is multithreaded and calling one endpoint does not stop another client from calling another endpoint, even if the first endpoint is still being served to the first client.

Experience #3 (the weird one)

  1. Open a browser tab with the Swagger page open,
  2. Open another browser tab with the Swagger page open,
  3. On the first Swagger page, call only TestSlow
  4. Quickly switch to the other Swagger page, call TestSlow there too

Result

  • The DebugController constructor enters immediately, then TestSlow starts immediately
  • When calling the other TestSlow, the Controller's contructor is not called before the first call to TestSlow is entirely finished and has returned!. In effect, the two calls to TestSlow happen sequentially.

Why is that? Why does multithreading suddenly seem to "disappear" the moment I try to call the same endpoint twice, even though I do it from two different clients?

jeancallisti
  • 1,046
  • 1
  • 11
  • 21
  • 1
    Have you tried the same experiments in Release mode? – Peter Csala Feb 01 '22 at 16:11
  • Have you tried to do it without debugging (and use logging/time measurements to determine the behaviour)? – Guru Stron Feb 01 '22 at 16:12
  • 4
    Do you have session state enabled? That's usually the culprit if you're seeing requests from the same browser being served serially rather than in parallel. *(NB: Two tabs in the same browser aren't "two different clients"; try calling from two different browsers instead.)* – Richard Deeming Feb 01 '22 at 16:19
  • Why are you using .ConfigureAwait(false)? That doesn't need to be done in an ASP.NET Core app. – mason Feb 01 '22 at 17:25
  • just a nit-pick here... "async" does not equal "multithreading". (Changes nothing about your question, though... async should be a non-blocking call.) – pcalkins Feb 01 '22 at 22:06
  • PeterCsala Guru Stron: No, I will try Release configuration. Richard deeming : I don't jnow if I have sesssion state enabled, I'll investigate that. Mason: just a coding practice in my company, don't ask. Pcalkins Indeed; here I'm expecting the multithreading to result from how Asp.Net manages controller instances, not from any specific async/await – jeancallisti Feb 02 '22 at 08:01
  • Running in Release doesn't change the behaviour. – jeancallisti Feb 02 '22 at 08:28
  • @RichardDeeming look at my accepted answer. – jeancallisti Feb 09 '22 at 08:42

1 Answers1

0

I most likely discovered by accident that session state disables simultaneous serving of the same endpoint called several times, as devised here:

Disable Session state per-request in ASP.Net MVC

So it appears that it isn't a multithreading problem, instead it's a web app configuration ("by design") problem.

@Richard deeming : Post this as an answer and I'll mark it as the correct one.

jeancallisti
  • 1,046
  • 1
  • 11
  • 21