I’ve seen this code lately:
static List<Thread> list = new List<Thread>();
static void Main(string[] args)
{
var lines = File.ReadAllLines(args[0]);
foreach (var line in lines)
{
StartThread(line);
}
Console.WriteLine("JOIN");
foreach (Thread thread in list)
{
thread.Join();
}
Console.WriteLine("END");
Console.ReadKey();
}
static void Upsert(object o)
{
var args = o.ToString().Split(',');
try
{
using (var con = new SqlConnection(Settings.Default.ConnString))
{
var cmd = new SqlCommand
{
Connection = con,
CommandText = "INSERT INTO Accounts VALUES(@p1, @p2, @p3, @p4, @p5)"
};
for (var index = 0; index < args.Length; index++)
{
cmd.Parameters.AddWithValue(@"@p" + (index + 1), args[index]);
}
try
{
con.Open();
cmd.ExecuteNonQuery();
Console.WriteLine("INSERTED");
}
catch (SqlException e)
{
switch (e.Number)
{
case 2627:
cmd.CommandText =
"UPDATE Accounts SET Name=@p2, Email=@p3, Active=@p4, Birthday=@p5 WHERE ID = @p1";
cmd.ExecuteNonQuery();
Console.WriteLine("UPDATED");
break;
case 1205:
StartThread(o); // On exception isn't some Thread handling should happen?
break;
}
}
}
}
}
private static void StartThread(object o)
{
// Is it correct to add another thread to the list again? when exception happens? What about the thread that was running
var t = new Thread(Upsert)
{
Priority = ThreadPriority.Highest,
IsBackground = true
};
list.Add(t);
t.Start(o);
Console.WriteLine("NEW THREAD STARTED");
}
I’m not so strong on threading, and I was wondering about that code specificaly when the error 1205 could occur and run another thread again with the same method adding to te thread list one more time. Shouldn’t be a check about the previous exception cought thread if it is finished and abort it? Then remove it from the list and add the new one?
Your contribution is really helpful.
Thank you.
You are right there are several problems with this code.
listis not synchronized in any way.I would scrap this code and create a stored procedure that does the upsert for you. Things like this are lot easier to deal with on the server side. Also, I am not particularly fond of the whole idea of multithreading for this situation anyway.
If you are wanting maximum speed out of this then read the file in via C# code and parse it out so that it is broken up into individual fields. Then use
SqlBulkCopyto throw all the records into a temporary landing zone table all at once. Finally, call a stored procedure to transfer the records in the temporary landing zone into the appropriate production table(s). All of this can be done without using any worker threads and it will probably be significantly faster as well.Update:
The code can be fixed easily if you use
CountdownEvent. Ditch the thread list entirely and instantiate aCountdownEventto do the waiting instead of callingJoinon all threads.Then change
StartThreadlike this.So what I am doing is initializing the
CoundownEventwith 1 count because I want to treat the main thread as if it were a worker as well. This will fix any subtle race conditions that might arise if one of the workers finishes before the main thread has finished spinning up all of the other threads. Each time I start a new thread I callAddCountand when that thread finishes I callSignal. And of course, the main thread waits for everything by callingWait.If I wanted to change the structure of the code a little more I would probably would have used tasks via
Taskand then when error 1205 comes I would have created a child task and attached it to the parent viaTaskCreationOptions.AttachedToParent. But, that would have required some more significant changes and I wanted to keep the changes to the minimum.