Async call to database inside for loop - c#

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;

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

C# index out of range, multi thread, Logs

I am trying to grab logs from windows. To make it faster I look for the days where logs are and then for that range of those days I open one thread per day to load it fast. In function work1 the error "Index was outside the bounds of the array" appears. If I make the job in only one thread it works fine but it is very very slow.
I tried to use the information from
"Index was outside the bounds of the array while trying to start multiple threads"
but it does not work.
I think the problem is in IEnumerable when it is loaded, like it is not loaded in time when the loop is started.
Sorry for my english, i am from Uzbekistan.
var result = from EventLogEntry elog in aLog.Entries
orderby elog.TimeGenerated
select elog.TimeGenerated;
DateTime OLDentry = result.First();
DateTime NEWentry = result.Last();
DTN.Add(result.First());
foreach (var dtn in result) {
if (dtn.Year != DTN.Last().Year |
dtn.Month != DTN.Last().Month |
dtn.Day != DTN.Last().Day
) {
DTN.Add(dtn);
}
}
List<Thread> t = new List<Thread>();
int i = 0;
foreach (DateTime day in DTN) {
DateTime yad = day;
var test = from EventLogEntry elog in aLog.Entries
where (elog.TimeGenerated.Year == day.Year) &&
(elog.TimeGenerated.Month == day.Month) &&
(elog.TimeGenerated.Day == day.Day)
select elog;
var tt2 = test.ToArray();
t.Add(new Thread(() => work1(tt2)));
t[i].Start();
i++;
}
static void work1(IEnumerable<EventLogEntry> b) {
var z = b;
for (int i = 0; i < z.Count(); i++) {
Console.Write(z + "\n");
}
}
Replace var tt2 = test; with var tt2 = test.ToArray();
The error is a mistake you do numerous times in your code: you are enumerating over a the data countless times. Calling .Count() enumerates the data again, and in this case the data ends up conflicting with cached values inside the EventLogEntry enumerator.
LINQ does not return a data set. It returns a query. A variable of type IEnumerable<T> may return different values every time you call Count(), First() or Last(). Calling .ToArray() makes C# retrieve the result and store it in an array.
You should generally just enumerate an IEnumerable<T> once.

Concurent foreach iteration of two list strings

Let's say I have two List<string>. These are populated from the results of reading a text file
List owner contains:
cross
jhill
bbroms
List assignee contains:
Chris Cross
Jack Hill
Bryan Broms
During the read from a SQL source (the SQL statement contains a join)... I would perform
if(sqlReader["projects.owner"] == "something in owner list" || sqlReader["assign.assignee"] == "something in assignee list")
{
// add this projects information to the primary results LIST
list_by_owner.Add(sqlReader["projects.owner"],sqlReader["projects.project_date_created"],sqlReader["projects.project_name"],sqlReader["projects.project_status"]);
// if the assignee is not null, add also to the secondary results LIST
// logic to determine if assign.assignee is null goes here
list_by_assignee.Add(sqlReader["assign.assignee"],sqlReader["projects.owner"],sqlReader["projects.project_date_created"],sqlReader["projects.project_name"],sqlReader["projects.project_status"]);
}
I do not want to end up using nested foreach.
The FOR loop would probably suffice. Someone had mentioned ZIP to me but wasn't sure if that would be a preferable route to go in my situation.
One loop to iterate through both lists (assuming both have same count):
for (int i = 0; i < alpha.Count; i++)
{
var itemAlpha = alpha[i] // <= your object of list alpha
var itemBeta = beta[i] // <= your object of list beta
//write your code here
}
From what you describe, you don't need to iterate at all.
This is what you need:
http://msdn.microsoft.com/en-us/library/bhkz42b3.aspx
Usage:
if ((listAlpga.contains(resultA) || (listBeta.contains(resultA)) {
// do your operation
}
List Iteration will happen implicitly inside the contains method. And thats 2n comparisions, vs n*n for nested iteration.
You would be better off with sequential iteration in each list one after the other, if at all you need to go that route.
This list is maybe better represented as a List<KeyValuePair<string, string>> which would pair the two list values together in a single list.
There are several options for this. The least "painful" would be plain old for loop:
for (var index = 0; index < alpha.Count; index++)
{
var alphaItem = alpha[index];
var betaItem = beta[index];
// Do something.
}
Another interesting approach is using the indexed LINQ methods (but you need to remember they get evaluated lazily, you have to consume the resulting enumerable), for example:
alpha.Select((alphaItem, index) =>
{
var betaItem = beta[index];
// Do something
})
Or you can enumerate both collection if you use the enumerator directly:
using (var alphaEnumerator = alpha.GetEnumerator())
using (var betaEnumerator = beta.GetEnumerator())
{
while (alphaEnumerator.MoveNext() && betaEnumerator.MoveNext())
{
var alphaItem = alphaEnumerator.Current;
var betaItem = betaEnumerator.Current;
// Do something
}
}
Zip (if you need pairs) or Concat (if you need combined list) are possible options to iterate 2 lists at the same time.
I like doing something like this to enumerate over parallel lists:
int alphaCount = alpha.Count ;
int betaCount = beta.Count ;
int i = 0 ;
while ( i < alphaCount && i < betaCount )
{
var a = alpha[i] ;
bar b = beta[i] ;
// handle matched alpha/beta pairs
++i ;
}
while ( i < alphaCount )
{
var a = alpha[i] ;
// handle unmatched alphas
++i ;
}
while ( i < betaCount )
{
var b = beta[i] ;
// handle unmatched betas
++i ;
}

Creating an array from scratch while code is running

I am trying to create a list via array like this:
private Application[] GetApps()
{
DataSet ds = new Dataset();
string query = "query";
ds = GetData(query);
var appList = new Application[ds.Tables[0].Rows.Count];
for(var i = 0; i <= ds.Tables[0].Rows.Count - 1; i++)
{
DataRow item = ds.Tables[0].Rows[i];
appList[i].Name = item["Name"].ToString();
appList[i].Confidentiality = int.Parse(item["Conf"].ToString());
appList[i].Id = item["ID"].ToString();
}
return appList;
}
I keep getting an object null error and I know I have to be missing something completely obvious that I'm just not seeing. Do I need to declare the new array in some other way?
When you create appList, you are only creating the array itself. .NET does not automatically populate the array with Application objects for you to manipulate. You need to create a new Application object, and set the properties on that object, then you can assign the object to the array.
There are multiple Application classes withing the .NET framework, none of which seem to match your code, so the below example will simply assume that Application is a custom type of your own design.
for(var i = 0; i < ds.Tables[0].Rows.Count; i++)
{
DataRow item = ds.Tables[0].Rows[i];
Appliction app = new Application();
app.Name = item["Name"].ToString();
app.Confidentiality = int.Parse(item["Conf"].ToString());
app.Id = item["ID"].ToString();
appList[i] = app
}
As an aside, note that you can replace i <= x - 1 with i < x and the behavior is exactly the same.
Finally, you should introduce checks for all of your accessors if there is a chance that they could return null. For example, if item["Name"] returns null, then calling item["Name"].ToString() is equivelant to calling null.ToString(), which will also result in a NullReferenceException.

Remove duplicate items from a List<String[]> in C#

I have an issue here a bit complex than I'm trying to resolve since some days ago. I'm using the PetaPoco ORM and didn't found any other way to do a complex query like this:
var data = new List<string[]>();
var db = new Database(connectionString);
var memberCapabilities = db.Fetch<dynamic>(Sql.Builder
.Select(#"c.column_name
,CASE WHEN c.is_only_view = 1
THEN c.is_only_view
ELSE mc.is_only_view end as is_only_view")
.From("capabilities c")
.Append("JOIN members_capabilities mc ON c.capability_id = mc.capability_id")
.Where("mc.member_id = #0", memberID)
.Where("c.table_id = #0", tableID));
var roleCapabilities = db.Fetch<dynamic>(Sql.Builder
.Select(#"c.column_name
,CASE WHEN c.is_only_view = 1
THEN c.is_only_view
ELSE rc.is_only_view end as is_only_view")
.From("capabilities c")
.Append("JOIN roles_capabilities rc ON c.capability_id = rc.capability_id")
.Append("JOIN members_roles mr ON rc.role_id = mr.role_id")
.Where("mr.member_id = #0", memberID)
.Where("c.table_id = #0", tableID));
I'm trying to get the user capabilities, but my system have actually to ways to assign an user a capability, or direct to that user or attaching the user to a role. I wanted to get this merged list using a stored procedure but I needed cursors and I thought maybe should be easier and faster doing this on the web application. So I get that two dynamics and the members capabilities have priority to the roles capabilities, so I need to check if that using loops. And I did like this:
for (int i = 0; i < roleCapabilities.Count; i++)
{
bool added = false;
for (int j = 0; j < memberCapabilities.Count; j++)
if (roleCapabilities[i].column_name == memberCapabilities[j].column_name)
{
data.Add(new string[2] { memberCapabilities[j].column_name, Convert.ToString(memberCapabilities[j].is_only_view) });
added = true;
break;
}
if (!added)
data.Add(new string[2] { roleCapabilities[i].column_name, Convert.ToString(roleCapabilities[i].is_only_view) });
}
So now the plan is delete the duplicate entries. I have try using the following methods with no results:
data = data.Distinct();
Any help? Thanks
Make sure that your object either implements System.IEquatable or overrides Object.Equals and Object.GetHashCode. In this case, it looks like you're storing the data as string[2], which won't give you the desired behavior. Create a custom object to hold the data, and do one of the 2 options listed above.
If I understand your question correctly you want to get a distinct set of arrays of strings, so if the same array exists twice, you only want one of them? The following code will return arrays one and three while two is removed as it is the same as one.
var one = new[] {"One", "Two"};
var two = new[] {"One", "Two"};
var three = new[] {"One", "Three"};
List<string[]> list = new List<string[]>(){one, two, three};
var i = list.Select(l => new {Key = String.Join("|", l), Values = l})
.GroupBy(l => l.Key)
.Select(l => l.First().Values)
.ToArray();
You might have to use ToList() after Distinct():
List<string[]> distinct = data.Distinct().ToList();

How to change each element of a List<long>?

I have a List of different DayTime (Ticks). I try to get a list of the time remaining from now to each time element.
List<long> diffliste = new List<long>(m_DummyAtTime);
// 864000000000 ≙ 24h
diffliste.ForEach(item => { item -= now; if (item < 0) item += 864000000000; });
// test, does also not work
// diffliste.ForEach(item => { item -= 500; });
However, the list is not changed. Do I miss something?
(now is DateTime.Now.TimeOfDay.Ticks)
var times = diffliste.Select(ticks => new DateTime(ticks) - DateTime.Now);
Will return a collection of TimeSpans between now and each time.
Without using Linq:
List<TimeSpan> spans = diffliste.ConvertAll(ticks => new DateTime(ticks) - DateTime.Now);
(modified as suggested by Marc)
You are changing a standalone copy in a local variable (well, parameter actually), not the actual value in the list. To do that, perhaps:
for(int i = 0 ; i < diffliste.Count ; i++) {
long val = diffliste[i]; // copy the value out from the list
... change it
diffliste[i] = val; // update the value in the list
}
Ultimately, your current code is semantically similar to:
long firstVal = diffliste[0];
firstVal = 42;
which also does not change the first value in the list to 42 (it only changes the local variable).
You cannot change the value of an item inside a foreach cycle.
You can do it using a classic for cycle or creating and assigning items to a new list.
for (int i = 0 ; i < diffliste.Count; i++)
{
long value = diffliste[i];
// Do here what you need
diffliste[i] = value;
}
The iteration var in a foreach cycle is immutable, so you cannot change it. You either have to create a new list or use a for cycle... see also here.

Categories

Resources