我有一个正在执行一些调用SQL Server的Windows服务应用程序。我有一个特定的工作单元要做,其中包括将一行保存到Message
表并更新Buffer
表中的多行。Intermittent System.ArgumentNullException使用TransactionScope
我已经将这两条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服务器)我间歇收到此错误信息:
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
的Dispose()
方法。我在这里遇到了一些问题,因为TransactionScope
课程的内部工作似乎引发了异常情况。
可能很重要的一件事是,在服务器上安装时,我必须启用分布式事务处理协调器的一些设置才能允许网络访问这让我想到,当它全部在我的本地计算机上时,DTC可能没有使用。
DTC可能是此异常原因的一部分吗?
我也考虑过是否要处理最大连接池,但会比我得到的更有用的异常。我继续运行this question中的查询来检查连接池大小,并且它从未超过四个。
我最终的问题是,为什么这个错误会间歇性地发生?我如何诊断导致它的原因?
编辑:线程
@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方法是否在后台线程(即与数据库访问相同的线程)上运行? – Joe