我有一个 Windows 服务应用程序,它正在执行一些对 SQL Server 的调用。我有一个特定的工作单元要做,其中包括将一行保存到Message
表中并更新表中的多行Buffer
。
我将这两个 SQL 语句包装成 aTransactionScope
以确保它们要么都被提交,要么都不被提交。
高级代码如下所示:
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
块关闭的地方,因此Dispose()
调用TransactionScope
. 我在这里有点不知所措,因为TransactionScope
该类的内部工作似乎引发了异常。
可能很重要的一件事是,在服务器上安装时,我必须启用分布式事务协调器的一些设置以允许网络访问这让我想到,当它全部在我的本地计算机上时,可能不使用 DTC .
DTC 可能是导致此异常的部分原因吗?
我还考虑了这是否与连接池被最大化有关,但预计会有一个比我得到的更有用的例外。我一直在这个问题中运行查询来检查连接池大小,它从未超过四个。
我的最终问题是,为什么这个错误会间歇性地发生?如何诊断是什么原因造成的?
编辑:线程
@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();
}
}