Async call to database inside for loop

Viewed 145

I am trying to make call to database and store result in record, stored proc always returns 4 records, but some time I got 3 records and reader shows 4 count but null in first record. What is wrong with code ?

List record = new List();

List<Task> listOfTasks = new List<Task>();

for (int i = 0; i < 2; i++)
{
    listOfTasks.Add(Task.Factory.StartNew(() => {
        IDataCommand cmd = ds.CreateCommand("DropTicket", "returnTableTypeData",
            CommandType.StoredProcedure);
        IDataReader reader = cmd.ExecuteReader();
        while (reader.Read())
        {
            TicketTextOutputRecord rec = new TicketTextOutputRecord();
            rec.ValidationNumber = (string)reader["ValidationNumber"];
            rec.IsSuccess = (bool)reader["IsSuccess"];
            rec.Error = (string)reader["Error"];
            record.Add(rec);
        }
        //reader.Close();
        //reader.Dispose();
    }));
}

Task.WaitAll(listOfTasks.ToArray());

return record;
2 Answers

This sounds like a concurrency error; it is not intended that a connection is accessed concurrently; you are allowed overlapping readers (if MARS is enabled), but the actual access must still not be concurrent in terms of multiple threads trying to do things at the same time. The moment you do that, all behavior is undefined. Frankly, I'd just execute these sequentially, not concurrently. You are allowed to work concurrently if you use completely unrelated connections, note.

I fixed exactly the same error before.

List is not thread safe.

When adding items concurrently, the internal pointer of list can get confused and can cause item to return null even if non null value was added.

This produces the problem:

var list = new List<object>();
var listOfTasks = new List<Task>();

for (var i = 0; i < 10; i++)
{
    listOfTasks.Add(Task.Factory.StartNew(() => list.Add(new object())));
}

Task.WaitAll(listOfTasks.ToArray());

Use a thread safe list will fix the problem. But I’d change the task to return the result rather than adding it to a list. Then use LINQ or Task.WhenAll to get those results.

var listOfTasks = new List<Task<object>>();

for (var i = 0; i < 10; i++)
{
    listOfTasks.Add(Task.Factory.StartNew(() => new object()));
}

var list = await Task.WhenAll(listOfTasks.ToArray());

// OR

Task.WaitAll(listOfTasks.ToArray());
var list = listOfTasks.Select(t => t.Result).ToList();
Related