当前位置: 动力学知识库 > 问答 > 编程问答 >

ado.net - Microsoft Robotics and Sql

问题描述:

I have an issue implementing CCR with SQL. It seems that when I step through my code the updates and inserts I am trying to execute work great. But when I run through my interface without any breakpoints, it seems to be working and it shows the inserts, updates, but at the end of the run, nothing got updated to the database.

I proceeded to add a pause to my code every time I pull anew thread from my pool and it works... but that defeats the purpose of async coding right? I want my interface to be faster, not slow it down...

Any suggestions... here is part of my code:

I use two helper classes to set my ports and get a response back...

 /// <summary>

/// Gets the Reader, requires connection to be managed

/// </summary>

public static PortSet<Int32, Exception> GetReader(SqlCommand sqlCommand)

{

Port<Int32> portResponse = null;

Port<Exception> portException = null;

GetReaderResponse(sqlCommand, ref portResponse, ref portException);

return new PortSet<Int32, Exception>(portResponse, portException);

}

// Wrapper for SqlCommand's GetResponse

public static void GetReaderResponse(SqlCommand sqlCom,

ref Port<Int32> portResponse, ref Port<Exception> portException)

{

EnsurePortsExist(ref portResponse, ref portException);

sqlCom.BeginExecuteNonQuery(ApmResultToCcrResultFactory.Create(

portResponse, portException,

delegate(IAsyncResult ar) { return sqlCom.EndExecuteNonQuery(ar); }), null);

}

then I do something like this to queue up my calls...

 DispatcherQueue queue = CreateDispatcher();

String[] commands = new String[2];

Int32 result = 0;

commands[0] = "exec someupdateStoredProcedure";

commands[1] = "exec someInsertStoredProcedure '" + Settings.Default.RunDate.ToString() + "'";

for (Int32 i = 0; i < commands.Length; i++)

{

using (SqlConnection connSP = new SqlConnection(Settings.Default.nbfConn + ";MultipleActiveResultSets=true;Async=true"))

using (SqlCommand cmdSP = new SqlCommand())

{

connSP.Open();

cmdSP.Connection = connSP;

cmdSP.CommandTimeout = 150;

cmdSP.CommandText = "set arithabort on; " + commands[i];

Arbiter.Activate(queue, Arbiter.Choice(ApmToCcrAdapters.GetReader(cmdSP),

delegate(Int32 reader) { result = reader; },

delegate(Exception e) { result = 0; throw new Exception(e.Message); }));

}

}

where ApmToCcrAdapters is the class name where my helper methods are...

The problem is when I pause my code right after the call to Arbiter.Activate and I check my database, everything looks fine... if I get rid of the pause ad run my code through, nothing happens to the database, and no exceptions are thrown either...

网友答案:

The problem here is that you are calling Arbiter.Activate in the scope of your two using blocks. Don't forget that the CCR task you create is queued and the current thread continues... right past the scope of the using blocks. You've created a race condition, because the Choice must execute before connSP and cmdSP are disposed and that's only going to happen when you're interfering with the thread timings, as you have observed when debugging.

If instead you were to deal with disposal manually in the handler delegates for the Choice, this problem would no longer occur, however this makes for brittle code where it's easy to overlook disposal.

I'd recommend implementing the CCR iterator pattern and collecting results with a MulitpleItemReceive so that you can keep your using statements. It makes for cleaner code. Off the top of my head it would look something like this:

private IEnumerator<ITask> QueryIterator(
    string command,
    PortSet<Int32,Exception> resultPort)
{
    using (SqlConnection connSP = 
        new SqlConnection(Settings.Default.nbfConn 
            + ";MultipleActiveResultSets=true;Async=true"))
    using (SqlCommand cmdSP = new SqlCommand())
    {
        Int32 result = 0;
        connSP.Open();
        cmdSP.Connection = connSP;
        cmdSP.CommandTimeout = 150;
        cmdSP.CommandText = "set arithabort on; " + commands[i];

        yield return Arbiter.Choice(ApmToCcrAdapters.GetReader(cmdSP),
            delegate(Int32 reader) { resultPort.Post(reader); },
            delegate(Exception e) { resultPort.Post(e); });
    }

}

and you could use it something like this:

var resultPort=new PortSet<Int32,Exception>();
foreach(var command in commands)
{
    Arbiter.Activate(queue,
        Arbiter.FromIteratorHandler(()=>QueryIterator(command,resultPort))
    );
}
Arbiter.Activate(queue,
    Arbiter.MultipleItemReceive(
        resultPort,
        commands.Count(),
        (results,exceptions)=>{
            //everything is done and you've got 2 
            //collections here, results and exceptions
            //to process as you want
        }
    )
);
分享给朋友:
您可能感兴趣的文章:
随机阅读: