SQL Server に実行時間の長いストアド プロシージャがあり、ユーザーがキャンセルできるようにする必要があります。SqlCommand.Cancel()
メソッドが非常にうまく機能することを示す次のような小さなテストアプリを作成しました。
private SqlCommand cmd;
private void TestSqlServerCancelSprocExecution()
{
TaskFactory f = new TaskFactory();
f.StartNew(() =>
{
using (SqlConnection conn = new SqlConnection("connStr"))
{
conn.InfoMessage += conn_InfoMessage;
conn.FireInfoMessageEventOnUserErrors = true;
conn.Open();
cmd = conn.CreateCommand();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "dbo.[CancelSprocTest]";
cmd.ExecuteNonQuery();
}
});
}
private void cancelButton_Click(object sender, EventArgs e)
{
if (cmd != null)
{
cmd.Cancel();
}
}
を呼び出すとcmd.Cancel()
、基になるストアド プロシージャの実行が実質的に即座に停止することを確認できます。アプリケーションで async/await パターンをかなり頻繁に使用することを考えると、そのSqlCommand
パラメーターの非同期メソッドがCancellationToken
同様にうまく機能することを期待していました。残念ながら、 を呼び出すCancel()
とCancellationToken
、InfoMessage
イベント ハンドラーが呼び出されなくなりましたが、基になるストアド プロシージャは引き続き実行されることがわかりました。非同期バージョンのテスト コードは次のとおりです。
private SqlCommand cmd;
private CancellationTokenSource cts;
private async void TestSqlServerCancelSprocExecution()
{
cts = new CancellationTokenSource();
using (SqlConnection conn = new SqlConnection("connStr"))
{
conn.InfoMessage += conn_InfoMessage;
conn.FireInfoMessageEventOnUserErrors = true;
conn.Open();
cmd = conn.CreateCommand();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "dbo.[CancelSprocTest]";
await cmd.ExecuteNonQueryAsync(cts.Token);
}
}
private void cancelButton_Click(object sender, EventArgs e)
{
cts.Cancel();
}
CancellationToken
がどのように機能するはずなのか、何かが欠けていますか? 問題が発生した場合に備えて、.NET 4.5.1 と SQL Server 2012 を使用しています。
編集:同期コンテキストが要因であり、同じ動作が見られる場合に備えて、テストアプリをコンソールアプリとして書き直しました-の呼び出しはCancellationTokenSource.Cancel()
、基になるストアドプロシージャの実行を停止しません。
編集:これは、重要な場合に呼び出すストアド プロシージャの本体です。レコードを挿入し、結果を 1 秒間隔で印刷して、キャンセルの試行がすぐに有効になったかどうかを簡単に確認できるようにします。
WHILE (@loop <= 40)
BEGIN
DECLARE @msg AS VARCHAR(80) = 'Iteration ' + CONVERT(VARCHAR(15), @loop);
RAISERROR (@msg,0,1) WITH NOWAIT;
INSERT INTO foo VALUES (@loop);
WAITFOR DELAY '00:00:01.01';
SET @loop = @loop+1;
END;