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の後に、メインスレッドの結合と新しいスレッドの作成の間に競合が発生します。

このコードを破棄して、アップサートを実行するストアドプロシージャを作成します。このようなことは、サーバー側での処理がはるかに簡単です。また、とにかく、私はこの状況でのマルチスレッドのアイデア全体が特に好きではありません。

これを最大限に活用したい場合は、C#コードを介してファイルを読み込み、解析して個々のフィールドに分割します。次に、を使用SqlBulkCopyして、すべてのレコードを一時的なランディングゾーンテーブルに一度にスローします。最後に、ストアドプロシージャを呼び出して、一時ランディングゾーンのレコードを適切な本番テーブルに転送します。これはすべて、ワーカースレッドを使用せずに実行でき、おそらく大幅に高速になります。

アップデート:

を使用すると、コードを簡単に修正できますCountdownEvent。スレッドリストを完全に破棄し、aをインスタンス化して、すべてのスレッド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");
}

CoundownEventつまり、メインスレッドもワーカーであるかのように扱いたいので、1カウントで初期化しています。これにより、メインスレッドが他のすべてのスレッドのスピンアップを完了する前に、ワーカーの1つが終了した場合に発生する可能性のある、微妙な競合状態が修正されます。新しいスレッドを開始するたびに呼び出しAddCount、そのスレッドが終了すると呼び出しますSignal。そしてもちろん、メインスレッドはを呼び出してすべてを待ちますWait

コードの構造をもう少し変更したい場合は、を介してタスクを使用しTask、エラー1205が発生したときに、子タスクを作成して、を介して親にアタッチしTaskCreationOptions.AttachedToParentます。しかし、それにはもっと重要な変更が必要だったので、変更を最小限に抑えたかったのです。

于 2012-05-10T14:20:33.440 に答える