1

我最近看到了这段代码:

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 并使用相同的方法再次运行另一个线程,再次添加到线程列表中。如果之前的异常咳嗽线程已完成并中止,不应该检查它吗?然后将其从列表中删除并添加新的?

你的贡献真的很有帮助。

谢谢你。

4

1 回答 1

2

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

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

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

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

更新:

如果您使用CountdownEvent. 完全放弃线程列表并实例化 aCountdownEvent以进行等待,而不是调用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");
}

所以我正在做的是CoundownEvent用 1 个计数来初始化,因为我想把主线程也当作一个工作线程来对待。如果其中一个工作人员在主线程完成所有其他线程的旋转之前完成,这将修复可能出现的任何微妙的竞争条件。每次我启动一个新线程时,我都会调用AddCount,当该线程完成时,我会调用Signal. 当然,主线程通过调用Wait.

如果我想稍微更改代码的结构,我可能会使用任务 via Task,然后当错误 1205 出现时,我会创建一个子任务并将其附加到父任务 via TaskCreationOptions.AttachedToParent。但是,这将需要一些更重大的更改,我希望将更改保持在最低限度。

于 2012-05-10T14:20:33.440 回答