admin管理员组

文章数量:1122832

Assume I'm using the following C# Minimal API endpoint:

private Calculator calcObj = new Calculator ();

app.MapGet("/calc", (int a, int b) =>
{
    var additionResult = calcObj.Addition(a, b);
    var multiplicationResult = calcObj.Multiplication(a, b);
    return Results.Ok(new Message() { Text = "Result for addition: " + additionResult.ToString() + "Result for multiplication: " + multiplicationResult.ToString() });
});

This can be called by thousands of users at the same time.

So if the first user calls the API with a=1, b=2 and the second user calls it with a=3, b=4 and the next user with ... and so on...

How is every user getting their personal result without getting the calculation result "destroyed" by the next user's input?

I know the .NET runtime uses a thread pool to manage threads. When a request comes in, a thread from the pool is assigned to handle it. Then it should theoretically be no problem, right?

Or do I have to implement some locking mechanism?

Remark: the MyCalculator is not using a database. It is built of structs and classes and C# data types only.

UPDATE: To make it more clear assume the calculator does the following (I know the members are not needed, just to dicuss the problem):

using System;

class Calculator 
{
    private double A;
    private double B
    public double Addition(a, b)
    {
        A=a;
        B=b;
        return A+B;
    }
    public double Multiplication(a, b)
    {
        A=a;
        B=b;
        return A*B;
    }
}

Assume I'm using the following C# Minimal API endpoint:

private Calculator calcObj = new Calculator ();

app.MapGet("/calc", (int a, int b) =>
{
    var additionResult = calcObj.Addition(a, b);
    var multiplicationResult = calcObj.Multiplication(a, b);
    return Results.Ok(new Message() { Text = "Result for addition: " + additionResult.ToString() + "Result for multiplication: " + multiplicationResult.ToString() });
});

This can be called by thousands of users at the same time.

So if the first user calls the API with a=1, b=2 and the second user calls it with a=3, b=4 and the next user with ... and so on...

How is every user getting their personal result without getting the calculation result "destroyed" by the next user's input?

I know the .NET runtime uses a thread pool to manage threads. When a request comes in, a thread from the pool is assigned to handle it. Then it should theoretically be no problem, right?

Or do I have to implement some locking mechanism?

Remark: the MyCalculator is not using a database. It is built of structs and classes and C# data types only.

UPDATE: To make it more clear assume the calculator does the following (I know the members are not needed, just to dicuss the problem):

using System;

class Calculator 
{
    private double A;
    private double B
    public double Addition(a, b)
    {
        A=a;
        B=b;
        return A+B;
    }
    public double Multiplication(a, b)
    {
        A=a;
        B=b;
        return A*B;
    }
}
Share Improve this question edited Nov 23, 2024 at 7:56 Guru Stron 140k11 gold badges162 silver badges205 bronze badges asked Nov 22, 2024 at 15:00 user1911091user1911091 1,3612 gold badges16 silver badges37 bronze badges 2
  • Each call into MapGet will have its own a and b, and also its own results, they are local - scoped to the function. However, calcObj is created outside the function, so there is just one of them, with one A and B. This will be shared across threads so it's totally feasible that user 2 can change A before user 1 uses it to calculate. The simplistic solution would be to create a new Calculator inside the function, but this pattern does not scale, the answers below suggest better patterns. – iakobski Commented Nov 23, 2024 at 14:05
  • And to address your question about locking: no, this is not what locking is for. Locking is when you do want to share something between threads, but you want to ensure it's only being accessed by one thread at a time. – iakobski Commented Nov 23, 2024 at 14:06
Add a comment  | 

2 Answers 2

Reset to default 2

In the current code Minimal API handler will use the captured shared instance of the calculator (via the lambda closure mechanism). Since the calculator is not stateless (it mutates internal state) then you will have all sorts of concurrency issues here unless you will do proper synchronization. So you have following options:

  1. Perform proper synchronization
  2. Create instance of calculator per in the handler (i.e. )
    app.MapGet("/calc", (int a, int b) =>
    {
        var calc  = new Calculator();
        var additionResult = calc.Addition(a, b);
        // ...
    });
    
  3. Use DI and register the Calculator as Scoped/Transient (depending on the need, Scoped will result in an instance shared per scope, which for handler is equal to the request) and resolve it in the handler : app.MapGet("/calc", (int a, int b, Calculator c) => ...

Concurrent users are only a problem if multiple users read and write[*] to the same memory. So:

  1. Is calcObj shared between multiple users?
  2. Does calcObj.Addition or calcObj.Multiplication write to any shared memory?
  3. Is there any form of synchronization that prevents concurrent access?

I'm not that familiar with ASP.NET or Minimal APIs, but I would guess that calcObj is shared. A more typical approach is to use dependency injection for things like calcObj. This lets you declare if the dependencies should be shared (i.e. singleton) or not (i.e. transient/scoped). This makes it much more explicit what your intentions are. Note the local variables, like additionResult, live on the stack, and are never shared.

So does the add/multiply modify calcObj or any other shared memory? Yes, A and B will be shared. But there is no good reason for A and B to exist, it would work just as well without these fields. It is generally a good idea to prefer "pure" methods, and immutable data structures. Both because these tend to be easier to reason about, and because they are thread safe by default.

As long as calcObj keeps a shared state it would need to be protected. The easiest way would be to just use a lock inside both methods. If you use a lock you should try to hold it for as short period as possible, this both helps with contention and to avoid deadlocks. But this is mostly a concern when actually doing meaningful amount of computations.

But the recommended approach is to avoid shared mutable state whenever possible. I.e. make dependencies like calcObject either transient or , preferably, immutable. If that is not possible you probably want to use a database. Databases will isolate any mutation and provide the necessary synchronization for you.

[*] Multiple concurrent writers are also a problem, but concurrent reads are fine.

本文标签: cHow does Minimal API handle concurrent requests from multiple usersStack Overflow