SQL Server에 대한 호출을 수행하는 Windows 서비스 응용 프로그램이 있습니다. 하나의 행을 Message
테이블에 저장하고 Buffer
테이블의 여러 행을 업데이트하는 것과 관련된 특정 작업 단위가 있습니다.TransactionScope를 사용하는 간헐적 인 System.ArgumentNullException
이 두 SQL 문을 TransactionScope
으로 랩핑하여 둘 다 커밋되거나 커밋되지 않도록합니다.
높은 수준의 코드는 다음과 같습니다
public static void Save(Message message)
{
using (var transactionScope = new TransactionScope())
{
MessageData.Save(message.TransactionType,
message.Version,
message.CaseNumber,
message.RouteCode,
message.BufferSetIdentifier,
message.InternalPatientNumber,
message.DistrictNumber,
message.Data,
message.DateAssembled,
(byte)MessageState.Inserted);
BufferLogic.FlagSetAsAssembled(message.BufferSetIdentifier);
transactionScope.Complete();
}
}
이 모든 로컬 SQL Server 설치 내 개발 시스템에 완벽하게 일했다. 나는이 오류 메시지가 점점 간헐적으로오전 서버에 Windows 서비스를 배포 (하지만 내 로컬 컴퓨터의 SQL Server에 다시 연결)에
: 나는 코드 줄을 믿습니다
System.ArgumentNullException: Value cannot be null.
at System.Threading.Monitor.Enter(Object obj)
at System.Data.ProviderBase.DbConnectionPool.TransactedConnectionPool.TransactionEnded(Transaction transaction, DbConnectionInternal transactedObject)
at System.Data.SqlClient.SqlDelegatedTransaction.SinglePhaseCommit(SinglePhaseEnlistment enlistment)
at System.Transactions.TransactionStateDelegatedCommitting.EnterState(InternalTransaction tx)
at System.Transactions.CommittableTransaction.Commit()
at System.Transactions.TransactionScope.InternalDispose()
at System.Transactions.TransactionScope.Dispose()
at OpenLink.Logic.MessageLogic.Save(Message message) in E:\DevTFS\P0628Temp\OpenLink\OpenLink.Logic\MessageLogic.cs:line 30
at OpenLinkMessageAssembler.OpenLinkMessageAssemblerService.RunService() in E:\DevTFS\P0628Temp\OpenLink\OpenLinkMessageAssembler\OpenLinkMessageAssemblerService.cs:line 99
은에 의해 언급되고 예외는 using
블록이 닫혀있는 곳이며, 따라서 메서드를 TransactionScope
이라고 부릅니다. 예외가 여기에있는 TransactionScope
클래스의 내부 동작에 의해 던져진 것처럼 보입니다.
중요한 것은 서버에 설치할 때 네트워크 액세스를 허용하는 Distributed Transaction Coordinator의 일부 설정을 활성화해야한다는 것입니다. 이것이 내 로컬 컴퓨터에있을 때 DTC 아마 사용되지 않습니다.
DTC가이 예외의 원인 일 수 있습니까?
나는 또한 연결 풀이 초과 된 것과 관련이 있는지 고려했지만 내가 얻는 것보다 더 유용한 예외가 예상됩니다. this question에서 쿼리를 계속 실행하여 연결 풀 크기를 확인했으며 절대 4를 초과하지 않았습니다.
궁극적 인 질문은이 오류가 간헐적으로 발생하는 이유입니다. 그 원인을 진단하려면 어떻게해야합니까?
편집 :
스레딩는 @Joe이이 스레딩 문제 일 수 있습니다 제안했다. 따라서 아래 Windows 서비스의 골격 코드에 문제가 있는지 확인했습니다.
EventLogger
클래스는 Windows 이벤트 로그에만 쓰고 SQL Server에는 연결하지 않습니다.
partial class OpenLinkMessageAssemblerService : ServiceBase
{
private volatile bool _isStopping;
private readonly ManualResetEvent _stoppedEvent;
private readonly int _stopTimeout = Convert.ToInt32(ConfigurationManager.AppSettings["ServiceOnStopTimeout"]);
Thread _workerThread;
public OpenLinkMessageAssemblerService()
{
InitializeComponent();
_isStopping = false;
_stoppedEvent = new ManualResetEvent(false);
ServiceName = "OpenLinkMessageAssembler";
}
protected override void OnStart(string[] args)
{
try
{
_workerThread = new Thread(RunService) { IsBackground = true };
_workerThread.Start();
}
catch (Exception exception)
{
EventLogger.LogError(ServiceName, exception.ToString());
throw;
}
}
protected override void OnStop()
{
// Set the global flag so it can be picked up by the worker thread
_isStopping = true;
// Allow worker thread to exit cleanly until timeout occurs
if (!_stoppedEvent.WaitOne(_stopTimeout))
{
_workerThread.Abort();
}
}
private void RunService()
{
// Check global flag which indicates whether service has been told to stop
while (!_isStopping)
{
try
{
var buffersToAssemble = BufferLogic.GetNextSetForAssembly();
if (!buffersToAssemble.Any())
{
Thread.Sleep(30000);
continue;
}
... // Some validation code removed here for clarity
string assembledMessage = string.Empty;
buffersToAssemble.ForEach(b => assembledMessage += b.Data);
var messageParser = new MessageParser(assembledMessage);
var message = messageParser.Parse();
MessageLogic.Save(message); // <-- This calls the method which results in the exception
}
catch (Exception exception)
{
EventLogger.LogError(ServiceName, exception.ToString());
throw;
}
}
_stoppedEvent.Set();
}
}
멀티 스레드 응용 프로그램입니까? 스레드간에 데이터베이스 연결을 공유하고있을 가능성이 있습니까? – Joe
두 개의 스레드가있는 Windows 서비스입니다. 하나의 스레드가 시작/중지 요청을 처리하고 실제로 모든 작업을 수행하는 새 백그라운드 스레드를 시작합니다. 그러나 첫 번째 스레드는 데이터베이스에 쓰지 않으며 백그라운드 스레드 만 쓰지 않습니다. –
스레드 안전 문제와 같은 냄새가 있으므로 물어볼 필요가 있습니다. 한 가지 배경 스레드 만있을 수 있다는 것을 절대 확신합니까? 모든 데이터베이스 액세스에서 연결을 열거 나 닫으시겠습니까? 그렇지 않으면 여러 연결이 동일한 연결을 사용하려고 할 때 연결을 다시 사용합니까? TransactionScope를 만드는 Save 메서드가 백그라운드 스레드 (즉, DB 액세스와 동일한 스레드)에서 실행됩니까? – Joe