另一个Thread中的异常处理会创建另一个线程

时间:2012-05-10 14:00:14

标签: multithreading c#-4.0 thread-safety

我最近看到了这段代码:

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");
}

我在线程方面不是那么强大,当我发现错误1205时,我想知道该代码是否具体,并且再次使用相同的方法再次运行另一个线程再添加到te线程列表。如果完成并中止它,不应该检查以前的异常cought线程?然后从列表中删除它并添加新的?

您的贡献非常有用。

谢谢。

1 个答案:

答案 0 :(得分:2)

你是对的,这个代码有几个问题。

  • list的访问不会以任何方式同步。
  • 主线程连接与错误1205之后创建的新线程之间存在竞争。

我会废弃此代码并创建一个为您执行upsert的存储过程。这样的事情在服务器端更容易处理。此外,无论如何,我并不特别喜欢这种情况下多线程的整体想法。

如果您想要最大速度,请通过C#代码读取文件并解析出来,以便将其分解为单个字段。然后使用SqlBulkCopy将所有记录一次性抛出到临时登陆区域表中。最后,调用存储过程将临时着陆区中的记录传输到相应的生产表中。所有这些都可以在不使用任何工作线程的情况下完成,并且它可能也会明显更快。

<强>更新

如果您使用CountdownEvent,则可以轻松修复代码。完全抛弃线程列表并实例化CountdownEvent进行等待,而不是在所有线程上调用Join

static CountdownEvent complete = new CountdownEvent(1);

static void Main(string[] args)
{
    var lines = File.ReadAllLines(args[0]);

    foreach (var line in lines)
    {
        StartThread(line);
    }

    Console.WriteLine("JOIN");

    complete.Signal();
    complete.Wait();

    Console.WriteLine("END");
    Console.ReadKey();
}

然后像这样更改StartThread

private static void StartThread(object o)
{
    complete.AddCount();
    var t = new Thread(
      () =>
      {
        try
        {
          Upsert(o);
        }
        finally
        {
          complete.Signal();
        }
      });
    t.Priority = ThreadPriority.Highest;
    t.IsBackground = true;
    t.Start();
    Console.WriteLine("NEW THREAD STARTED");
}

所以我正在做的是用{1}来初始化CoundownEvent,因为我想把主线程看作是一个工作者。这将解决在主线程完成所有其他线程旋转之前,如果其中一个工作程序完成可能出现的任何微妙的竞争条件。每次我开始一个新线程时,我都会调用AddCount,当该线程结束时,我会调用Signal。当然,主线程通过调用Wait来等待所有事情。

如果我想更改代码的结构,我可能会通过Task使用任务,然后当出现错误1205时,我会创建一个子任务并通过{将其附加到父级{1}}。但是,这需要进行一些更重要的更改,我希望将更改保持在最低限度。