Intermittent System.ArgumentNullException using TransactionScope

女生的网名这么多〃 提交于 2020-01-14 14:17:07

问题


I have a Windows Service application which is performing some calls to SQL Server. I have a particular unit of work to do which involves saving one row to the Message table and updating multiple rows in the Buffer table.

I have wrapped these two SQL statements into a TransactionScope to ensure that they either both get committed, or neither get committed.

The high level code looks like this:

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();
    }
}

This has all worked perfectly on my development machine with a local SQL Server installation.

On deploying the Windows Service to a server (but connecting back to my local machine's SQL Server) I am intermittently getting this error message:

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

I believe the line of code being referred to by the exception is where the using block is closed, thus calling the Dispose() method of the TransactionScope. I'm at a bit of a loss here, as the exception seems to be thrown by the internal workings of the TransactionScope class.

One thing that may be significant is that when installing on the server, I had to enable some of the settings for the Distributed Transaction Coordinator to allow network access This got me into thinking that when it's all on my local machine, DTC is probably not used.

Could DTC be part of the cause of this exception?

I also considered whether it was to do with connection pools being maxed out, but would expect a more useful exception than what I'm getting. I kept running the query in this question to check the connection pool size, and it never exceeded four.

My ultimate question is, why is this error intermittently occurring? How can I diagnose what's causing it?

Edit: Threading

@Joe suggested this could be a threading issue. I've therefore included the skeleton code of my Windows Service below to see if it is problematic.

Note that the EventLogger class writes only to the Windows event log and does not connect to 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();
    }
}

回答1:


Check you have setup Your your web server and separate db server if you have them separate.

http://itknowledgeexchange.techtarget.com/sql-server/how-to-configure-dtc-on-windows-2008/

For Logging i would Suggest put int a try catch inside the transaction scope However if you logging to database you will need to make use of transaction scope suppress function

 using(TransactionScope scope4 = new 
        TransactionScope(TransactionScopeOption.Suppress)) 
    {
     ...
    } 



回答2:


I worked around this by stopping the transaction from being escalated to DTC. By using SQL 2008 instead of SQL 2005, the transaction does not get escalated, and all is fine.




回答3:


You do not mention your .Net version but according to http://support.microsoft.com/kb/960754, there is an issue with 2.50727.4016 version of System.Data.dll.

If your server has this older version, I would try to get the updated one from Microsoft.



来源:https://stackoverflow.com/questions/12427591/intermittent-system-argumentnullexception-using-transactionscope

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!