【发布时间】:2012-09-14 15:28:20
【问题描述】:
我有一个 Windows 服务应用程序正在执行对 SQL Server 的一些调用。我有一个特定的工作单元要做,其中包括将一行保存到Message 表中并更新Buffer 表中的多行。
我已将这两个 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 的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();
}
}
【问题讨论】:
-
它是多线程应用程序吗?您是否有可能在线程之间共享数据库连接?
-
这是一个有两个线程的 Windows 服务。一个线程处理启动/停止请求并触发一个新的后台线程,该线程实际上完成了所有工作。但是第一个线程从不写入数据库,只有后台线程。
-
它确实闻起来像一个线程安全问题,所以我不得不问:你确定只有一个后台线程吗?您是在每次数据库访问时打开/关闭连接(应该如此)还是重用连接(如果多个线程尝试使用相同的连接,这可能会导致这种情况)?创建 TransactionScope 的 Save 方法是否在后台线程(即与 DB 访问相同的线程)上运行?
-
嗨@Joe,我的问题已经更新为可以看到线程的主要结构。
标签: c# .net transactionscope msdtc