0

I need to have a POST controller handling a lot of traffic I've done something like that

    [HttpPost]
    public async Task<IActionResult> Post([FromBody] List<VdoPlayInfo> lv)
    {
        var seesionID = HttpContext.Session.GetString("sessionId");
        var vdoID = HttpContext.Session.GetInt32("videoid");
        var tasks = new Task[lv.Count];
        for (int i = 0; i < lv.Count; i++)
        {
            tasks[i] = Task.Run(() => _ADB.SaveLogView(lv[i]);                    
        }
        await Task.WhenAll(tasks);
        return Ok();          
    }

after several seconds of working I get this error

System.ArgumentOutOfRangeException: 'Index was out of range. 
Must be non-negative and less than the size of the collection.'

even tough the lv.Count = 2 I get the i index reaching 5. Whats the problem?

Set
  • 47,577
  • 22
  • 132
  • 150
Kulpemovitz
  • 531
  • 1
  • 9
  • 23

2 Answers2

0

Could you try this?

for (int i = 0; i < lv.Count; i++)
    {
        var item = lv[i];
        tasks[i] = Task.Run(() => _ADB.SaveLogView(item);                    
    }
valcarcel
  • 1
  • 1
0

It's not clear what you're ultimately trying to do here, but if your goal is high performance, this is the absolute wrong way to achieve that. Task.Run is going to pull a new thread from the pool each time you call it, so with two items in your list, your action is now consuming three threads (one for the request and one for each list item). Obviously, as the list items scale, so does your thread usage, so you're dramatically cutting the potential throughput of your server, and may actually end up being thread-starved.

It's not clear what _ADB.SaveLogView is doing, but if it does synchronous work, you need to realize that using Task.Run doesn't make it async. Sync is still sync, you're just blocking a different thread instead of the request thread. However, since you're awaiting task completion, the request thread is still blocked, so you're actually not achieving anything other than just wasting a bunch of threads that could be handling other requests.

If _ADB.SaveLogView is async (in which case you should name it _ADB.SaveLogViewAsync to avoid confusion), then you can simplify your code (and avoid wasting threads) by just doing:

var tasks = new Task[lv.Count];
for (int i = 0; i < lv.Count; i++)
{
    tasks[i] = _ADB.SaveLogViewAsync(lv[i]);                    
}

Since it should already be returning a Task in that scenario, there's no need to wrap it in another Task.

However, ideally, you should actually being handling this transactionally. If there's a problem saving one of these entities, the others still get saved. That can cause problems then if you need to resubmit to save the entity or entities that failed. Again, it's not clear what this method is doing, but with something like Entity Framework, you would simply add each new entity to the DbSet (which marks them as to be created in the change tracking) and then call SaveChangesAsync just once, which would attempt to save all the items in one go, wrapped in a transaction, so if any fail, everything gets rolled back.

If you're using Entity Framework or some other system that supports transactions under the hood, you should be utilizing that functionality. If you're doing some sort of manual database work, then you should roll your own transactions.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • Thank you Chris - great stuff. "SaveLogView" is a simple function to store the data into the SQL server using Dapper and Stored Procedure. for making the store procedure async is just adding async in the function line? – Kulpemovitz Nov 06 '17 at 09:56
  • No. You make it async by calling async methods. Dapper supports async usage, so you just need to utilize it that way. – Chris Pratt Nov 06 '17 at 12:28