10

タイトルはできるだけ具体的にするようにしました。基本的に、backgroundworker スレッド内で実行しているのは、次のようなコードです。

 SqlConnection conn = new SqlConnection(connstring);
                    SqlCommand cmd = new SqlCommand(query, conn);
                    conn.Open();
                    SqlDataAdapter sda = new SqlDataAdapter(cmd);
                    sda.Fill(Results);
                    conn.Close();
                    sda.Dispose();

query は大規模で時間のかかるクエリを表す文字列で、conn は接続オブジェクトです。

私の問題は、停止ボタンが必要なことです。クエリがキャンセルされた後に残っている結果を保持したいので、バックグラウンドワーカーを殺しても意味がないことに気づきました。さらに、クエリが完了するまでキャンセルされた状態を確認することはできません。

私がこれまでに思いついたこと:

パフォーマンスに大きな影響を与えることなく、これを効率的に処理する方法を概念化しようとしています。

私の考えは、SqlDataReader を使用して一度にクエリ ピースからデータを読み取って、ボタンを介して GUI から設定できるフラグをチェックするための「ループ」を作成することでした。問題は、私が知る限り、データテーブルの Load() メソッドを使用できず、sqlcommand をキャンセルできることです。私が間違っている場合はお知らせください。そうすればキャンセルが少し楽になります。

私が発見したことに照らして、以下のようなことをした場合にのみ、クエリの途中でsqlcommandをキャンセルできることに気づきました(疑似コード):

while(reader.Read())
{
 //check flag status
 //if it is set to 'kill' fire off the kill thread

 //otherwise populate the datatable with what was read
}

しかし、これは非常に非効率的で、コストがかかる可能性があるように私には思えます。これは、絶対にデータテーブルにある必要がある進行中の sqlcommand を強制終了する唯一の方法ですか? どんな助けでも大歓迎です!

4

2 に答える 2

5

実際には、キャンセルが問題となる2 つの段階があります。

  1. 最初の行が返される前に最初のクエリの実行をキャンセルする
  2. 行が提供されたときに行を読み取るプロセスを中止する

実際の sql ステートメントの性質によっては、これらの手順のいずれかが 99% の確率で実行される可能性があるため、両方を考慮する必要があります。たとえば、10 億行のテーブルを呼び出すと、実行SELECT *にはほとんど時間がかかりませんが、読み取りには非常に長い時間がかかります。逆に、調整が不十分なテーブルで非常に複雑な結合を要求し、それをいくつかの集計句でラップすると、実行に数分かかる場合がありますが、実際に返された少数の行を読み取るのにかかる時間はごくわずかです。

よく調整された高度なデータベース エンジンは、複雑なクエリに対して一度に行のチャンクをキャッシュするため、エンジンが行の次のバッチでクエリを実行している一時停止と、次のバッチを返すときにデータの高速バーストが交互に表示されます。結果の。

クエリ実行のキャンセル

実行中にクエリをキャンセルできるようにするには、SqlCommand.BeginExecuteReader のオーバーロードの 1 つを使用しクエリを開始し、SqlCommand.Cancelを呼び出して中止します。または、あるスレッドで ExecuteReader() を同期的に呼び出し、別のスレッドから Cancel() を呼び出すこともできます。ドキュメントにはたくさんのコード例があるため、コード例は含めません。

読み取り操作の中止

ここでは、単純なブール値フラグを使用するのがおそらく最も簡単な方法です。また、オブジェクトの配列を受け取る Rows.Add() オーバーロードを使用して、データ テーブルの行を埋めるのは非常に簡単です。つまり、次のようになります。

object[] buffer = new object[reader.FieldCount]
while(reader.Read()) {
    if(cancelFlag) break;
    reader.GetValues(buffer);
    dataTable.Rows.Add(buffer);
}

Read() へのブロッキング呼び出しのキャンセル

前述のように、reader.Read() の呼び出しによって、データベース エンジンが集中的な処理の別のバッチを実行する場合、一種の混合ケースが発生します。MSDN のドキュメントに記載されているようにRead()、この場合、元のクエリが で実行された場合でも、 への呼び出しがブロックされる可能性がありますBeginExecuteReaderRead()すべての読み取りを処理している 1 つのスレッドを呼び出すが、別のスレッドを呼び出すことで、これを回避できCancel()ます。リーダーがブロッキング呼び出しにあるかどうかを知る方法はRead、監視スレッドが読み取っている間にリーダー スレッドが更新する別のフラグを設定することです。

...
inRead = true
while(reader.Read()) {
    inRead = false
    ...
    inRead = true
}

// Somewhere else:
private void foo_onUITimerTick(...) {
   status.Text = inRead ? "Waiting for server" : "Reading";
}

Reader vs Adapterの性能について

通常、DataReader は を使用するよりも高速ですDataAdapter.Fill()。DataReader の全体的なポイントは、読み取りに対して非常に高速で応答性が高いことです。行ごとにブール値フラグを 1 回チェックしても、数百万行を超えても測定可能な時間差は追加されません。

大きなデータベース クエリの制限要因は、ローカルの CPU 処理時間ではなく、I/O パイプのサイズ (リモート データベースのネットワーク接続またはローカル データベースのディスク速度) または db サーバー自身のディスクの組み合わせです。複雑なクエリの速度と CPU 処理時間。DataAdapter と DataReader はどちらも、次の行が提供されるまで一度に数ナノ秒待機するだけで (おそらくほとんどの時間) 時間を費やします。

の便利さの 1 つDataAdapter.Fill()は、クエリ結果に一致するように DataTable 列を動的に生成する魔法を実行することですが、それを自分で行うのは難しくありません ( SqlDataReader.GetSchemaTable()を参照)。

于 2012-06-28T13:16:38.947 に答える
0

ちょっとお試し

時間のかかるクエリをBackgroundWorkerに入れて、コマンドを渡すことをお勧めします。コマンドオブジェクトを制御できるようにします。キャンセルコマンドが来たら、(進行中のBackgroundWorkerに)渡されたコマンドを言ってキャンセルしますcommand.Cancel()

于 2012-06-28T09:14:23.577 に答える