Поскольку вы используете SqlConnection
с пулами, ваш код никогда не контролирует закрытие соединений.Бассейн есть.На стороне сервера ожидающая транзакция будет откатана, когда соединение действительно закрыто (сокет закрыт), но при объединении в пул серверная сторона никогда не видит закрытие соединения.Без закрытия соединения (физическим разъединением на уровне сокета / канала / LPC или вызовом sp_reset_connection
) сервер не может прервать ожидающую транзакцию.Так что это действительно сводится к тому, что соединение не получает должным образом разъединить / сбросить.Я не понимаю, почему вы пытаетесь усложнить код явным прерыванием прерывания потока и пытаетесь открыть закрытую транзакцию (это никогда не сработает).Вы должны просто обернуть SqlConnection в блок using(...)
, подразумевается, что finally и Dispose для соединения будут выполняться даже при прерывании потока.
Я бы порекомендовал сохранить простоту, отказаться от обработки прерывания причудливой нити и заменить ее простым блоком 'using' (using(connection) {using(transaction) {code; commit () }}
.
Конечно, я предполагаю, что вы не распространяете контекст транзакции в другую область на сервере (вы не используете sp_getbindtoken
и друзей и не регистрируетесь в распределенных транзакциях),
Эта небольшая программа показывает, что Thread.Abort правильно закрывает соединение и откат транзакции:
using System;
using System.Data.SqlClient;
using testThreadAbort.Properties;
using System.Threading;
using System.Diagnostics;
namespace testThreadAbort
{
class Program
{
static AutoResetEvent evReady = new AutoResetEvent(false);
static long xactId = 0;
static void ThreadFunc()
{
using (SqlConnection conn = new SqlConnection(Settings.Default.conn))
{
conn.Open();
using (SqlTransaction trn = conn.BeginTransaction())
{
// Retrieve our XACTID
//
SqlCommand cmd = new SqlCommand("select transaction_id from sys.dm_tran_current_transaction", conn, trn);
xactId = (long) cmd.ExecuteScalar();
Console.Out.WriteLine("XactID: {0}", xactId);
cmd = new SqlCommand(@"
insert into test (a) values (1);
waitfor delay '00:01:00'", conn, trn);
// Signal readyness and wait...
//
evReady.Set();
cmd.ExecuteNonQuery();
trn.Commit();
}
}
}
static void Main(string[] args)
{
try
{
using (SqlConnection conn = new SqlConnection(Settings.Default.conn))
{
conn.Open();
SqlCommand cmd = new SqlCommand(@"
if object_id('test') is not null
begin
drop table test;
end
create table test (a int);", conn);
cmd.ExecuteNonQuery();
}
Thread thread = new Thread(new ThreadStart(ThreadFunc));
thread.Start();
evReady.WaitOne();
Thread.Sleep(TimeSpan.FromSeconds(5));
Console.Out.WriteLine("Aborting...");
thread.Abort();
thread.Join();
Console.Out.WriteLine("Aborted");
Debug.Assert(0 != xactId);
using (SqlConnection conn = new SqlConnection(Settings.Default.conn))
{
conn.Open();
// checked if xactId is still active
//
SqlCommand cmd = new SqlCommand("select count(*) from sys.dm_tran_active_transactions where transaction_id = @xactId", conn);
cmd.Parameters.AddWithValue("@xactId", xactId);
object count = cmd.ExecuteScalar();
Console.WriteLine("Active transactions with xactId {0}: {1}", xactId, count);
// Check count of rows in test (would block on row lock)
//
cmd = new SqlCommand("select count(*) from test", conn);
count = cmd.ExecuteScalar();
Console.WriteLine("Count of rows in text: {0}", count);
}
}
catch (Exception e)
{
Console.Error.Write(e);
}
}
}
}