7

私の問題: 今週の初めに、プログラムのタスクを高速化するタスクを受け取りました。私はそれを見て、そのタスクの関数に並列 foreach ループを使用するというアイデアをすぐに思いつきました。

私はそれを実装し、関数(すべてのサブ関数を含む)を調べ、SqlConnections(およびその他のもの)を変更して、並行して実行できるようにしました。私はすべてを開始し、すべてがうまくいきました(そのタスクの時間を最大 45% 短縮しただけです)

さて、昨日、もっとデータを使って同じことを試してみたかったのですが...奇妙な問題が発生しました:並列関数が呼び出されるたびに、それは機能しました...しかし、スレッドの1つが少なくとも4秒間ハングすることがありました分 (接続 AND コマンドの場合、タイムアウトは 1 分に設定されます)。

その間にプログラムを一時停止すると、そのループからまだアクティブなスレッドが1つだけで、ハングアップしていることがわかります

connection.Open()

〜 4 分後、プログラムはエラーをスローすることなく単純に続行します (どこかで例外が発生したが、アプリケーションではキャッチされず、SqlConnection/SqlCommand オブジェクトのどこかで例外がキャッチされたことを示す出力ボックスのメッセージは別として)。

何も起こらずに MSSQLServer のすべての接続を強制終了できます。また、MSSQLServer はその 4 分間何もせず、すべての接続がアイドル状態です。

これは、Update/Insert/Delete ステートメントをデータベースに送信するために使用される手順です。

int i = 80;
bool itDidntWork = true;
Random random = new Random();
while (itDidntWork && i > 0)
{
    try
    {
        using (SqlConnection connection = new SqlConnection(sqlConnectionString))
        {
            connection.Open();
            lock (connection)
            {
                command.Connection = connection;
                command.ExecuteNonQuery();
            }

            itDidntWork = false;
        }
    }
    catch (Exception ex)
    {
        if (ex is SqlException && ((SqlException)ex).ErrorCode == -2146232060)
        {
            Thread.Sleep(random.Next(500, 5000));
        }
        else
        {
            SqlConnection.ClearAllPools();
        }

        Thread.Sleep(random.Next(50, 110));
        i--;
        if (i == 0)
        {
            writeError(ex);
        }
    }
}

念のため: 小規模なデータベースではデッドロックが発生する可能性があるため (エラー番号 2146232060)、デッドロックが発生した場合は、衝突するステートメントを別の時間に発生させる必要があります。小規模なデータベースや小規模なサーバーでもうまく機能します。エラーの原因がデッドロックでない場合は、接続に問題がある可能性が高いため、切断されたすべての接続を消去しています。

スカラーの実行、データテーブル/データセットへの入力 (そうです、アプリケーションはそれほど古いものです)、およびストアドプロシージャの実行のための同様の関数が存在します

はい、それらはすべて並列ループで使用されます。

誰かがそこで何が起こっているのか考えていますか? または、そこで何が起こっているのかを知る方法についてのアイデアはありますか?

*コマンドオブジェクトについて編集:

コマンドオブジェクトは、関数に与えられると常に新しいオブジェクトになります。

ロックについて: ロックを外すと、Open() 関数が .NET の接続プールから接続を取得するだけなので、「接続が閉じられています」または「接続が既に開いています」というエラーが何十、何百も発生します。ロックは意図したとおりに機能します。

コード例:

using(SqlCommand deleteCommand = new SqlCommand(sqlStatement))
{
    ExecuteNonQuerySafely(deleteCommand); // that's the function that contains the body I posted above
}

*編集2

私は修正をしなければなりません:それはこれにかかっています

command.Connection = connection;

アプリケーションを一時停止すると、「ステップ」マークが緑色でオンになるため、少なくともそうだと思います

command.ExecuteNonQuery();

それが次に実行されるステートメントであると言っています。

*接続オブジェクトの周りにロックなしで別のテストを開始したことを確認するために3を編集します...結果を得るには数分かかります。

*編集4、私は間違っていました。ロックステートメントを削除しましたが...それでも機能しました。初めて試したときは、再利用された接続か何かがあったのかもしれません。ご指摘ありがとうございます。

*編集 5 これは、特定のデータベース プロシージャへの 1 つの特定の呼び出しでのみ発生するように感じています。どうしてか分かりません。C#に関しては、その呼び出しと他の呼び出しに違いはありません edit 6 を参照してください。そして、その時点でステートメントを実行しなかったので(推測します。誰かが私を修正できるかもしれません。デバッグモードで、行が(黄色ではなく)緑色でマークされている場合、そのステートメントはまだ実行されていませんが、待機しますその行の前のステートメントが終了するのは正しいですか?) 奇妙です。

*編集 6 全体で再利用された 3 つのコマンド オブジェクトがありました。それらは並列関数の上で定義されました。それがどれほど悪い/だったのか、私にはわかりません。これらは、1 つのストアド プロシージャを呼び出すためにのみ使用されました (それぞれが異なるプロシージャを呼び出しました)。もちろん、異なるパラメータと新しい接続 (上記のメソッドを介して) を使用します。

*編集 7 わかりました。実際には、特定のストアド プロシージャが 1 つ呼び出されたときだけです。それがハングする接続オブジェクトの割り当て上にあることを除いて (次の行は緑色でマークされています)。その原因が atm であるかを突き止めようとしています。

*編集 8 イェーイ、別のコマンドで発生しました。そういうことでした。

*編集 9 わかりました。問題が解決しました。「ハング」は、実際には 10 分 (!) に設定された CommandTimeout でした。それらは 2 つのコマンド (編集 7 で言及したものと編集 8 で言及したもの) に対してのみ設定されました。devundef が示唆するようにコマンドを再構築しているときに両方を見つけたので、彼の答えを私の問題を解決したものとしてマークしました。また、for ループが使用するスレッドの量を制限するという彼の提案により、プロセスがさらに高速化されました。

説明をしてくれた Marc Gravell に特に感謝し、土曜日に私と一緒にここに立ち寄ってくれました ;)

4

1 に答える 1

4

edit 6: edit 6: ...3 command objects were reused the whole timeで問題が見つかると思います。

並列ループ内で使用されるすべてのデータは、ループ内で作成するか、一度に 1 つのスレッドのみがその特定のオブジェクトにアクセスできるように適切な同期コードを配置する必要があります。内にそのようなコードは見当たりませんExecuteNonQuerySafely。a) 接続オブジェクトはメソッド内で作成されるため、接続をロックしてもそこでは効果がありません。b) コマンドをロックしても、スレッドの安全性は保証されません。おそらく、メソッド内でロックする前にコマンド パラメータを設定しています。lock(command)を呼び出す前にコマンドをロックするとAは機能ExecuteNonQuerySafelyしますが、並列ループ内でロックすることは良いことではありません。これは逆並列の定義であるため、これを完全に回避し、反復ごとに新しいコマンドを作成することをお勧めします。さらに良いのは、少しリファクタリングすることですExecuteNonQuerySafely、SqlCommand の代わりにコールバック アクションを受け入れることができます。例:

public void ExecuteCommandSafely(Action<SqlCommand> callback) {
   ... do init stuff ...
   using (var connection = new SqlConnection(...)) {
      using (var command = new SqlCommand() {
         command.Connection = connection;
         try{
            callback(command);
         }
         ... error handling stuff ...
      }          

   }

}

そして使用:

ExecuteCommandSafely((command) => {
   command.CommandText = "...";
   ... set parameters ..
   command.ExecuteNonQuery();
});

最後に、コマンドを並列実行するとエラーが発生するという事実は、この場合、並列実行が適切でない可能性があることを示しています。エラーを取得するためにサーバー リソースを浪費しています。接続は高価です。MaxDegreeOfParalellismオプションを使用して、この特定のループのワークロードを調整してみてください (最適な値は、ハードウェア/サーバー/ネットワークなどに応じて変化することに注意してください)。メソッドには、そのパーに対して並列に実行するスレッドの数を設定できるパラメーターParallel.ForEachを受け入れるオーバーロードがあります ( http://msdn.microsoft.com/en-us/library/system.threading.tasks.paralleloptions. maxdegreeofparallelism.aspx )。ParallelOptions

于 2012-08-18T20:08:18.143 に答える