我有一個正在執行一些調用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