奇怪的 CLR/编译器行为在发布模式下,但在调试模式下不是
本文关键字:模式 调试 布模式 CLR 编译器 | 更新日期: 2023-09-27 17:56:32
我正在为Windows CE和.NET Compact Framework 3.5开发一个应用程序。
代码在调试模式下运行良好,但是当我将模式更改为发布时,我会出现各种异常。我认为这与编译器试图在发布模式下实现的一些优化有关,例如过早地处理和垃圾收集对象。
以下方法,尝试将实体插入 Sql Compact 数据库,抛出两个异常(我认为是随机的):
public int Add<T>(List<T> entities)
{
int rowsEffected = 0;
EntityMetadata metadata = GetMetadata<T>();
using (SqlCeCommand command = new SqlCeCommand())
{
command.Connection = connection;
command.CommandType = CommandType.TableDirect;
command.CommandText = metadata.EntityMapAttribute.TableName;
SqlCeResultSet set = command.ExecuteResultSet(ResultSetOptions.Scrollable | ResultSetOptions.Updatable);
// Get generated Id, used in the loop below
command.CommandType = CommandType.Text;
command.CommandText = "SELECT @@IDENTITY";
foreach (var entity in entities)
{
SqlCeUpdatableRecord record = set.CreateRecord();
PropertyMetadata pkPropertyMetadata = null;
foreach (var prop in metadata.PropertyMetadataList)
{
if (prop.Attribute.IsPK)
{
// Identify PK Property, avoid setting values (db automatically sets id)
pkPropertyMetadata = prop;
}
else
{
object columnValue = prop.GetAccesssorDelegates<T>().Getter(entity);
record.SetValue(prop.Attribute.ColumnNumber, columnValue);
}
}
set.Insert(record);
// Get Id of the inserted entity
if (pkPropertyMetadata != null)
{
object rawid = command.ExecuteScalar();
object convertedId = Convert.ChangeType(rawid, pkPropertyMetadata.Attribute.ColumnType, null);
pkPropertyMetadata.GetAccesssorDelegates<T>().Setter(entity, convertedId);
}
rowsEffected++;
}
return rowsEffected;
}
}
例外 1:
Test 'M:Babil04_Mobil.Tests.ORMTests.Engine_Works' failed: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
at System.Data.SqlServerCe.NativeMethods.ExecuteQueryPlan(IntPtr pTx, IntPtr pQpServices, IntPtr pQpCommand, IntPtr pQpPlan, IntPtr prgBinding, Int32 cDbBinding, IntPtr pData, Int32& recordsAffected, ResultSetOptions& cursorCapabilities, IntPtr& pSeCursor, Int32& fIsBaseTableCursor, IntPtr pError)
at System.Data.SqlServerCe.SqlCeCommand.ExecuteCommandText(IntPtr& pCursor, Boolean& isBaseTableCursor)
at System.Data.SqlServerCe.SqlCeCommand.ExecuteCommand(CommandBehavior behavior, String method, ResultSetOptions options)
at System.Data.SqlServerCe.SqlCeCommand.ExecuteScalar()
MORMEngine.cs(182,0): at MORM.MORMEngine.Add[T](List`1 entities)
Tests'ORMTests.cs(187,0): at Babil04_Mobil.Tests.ORMTests.Engine_Works()
例外 2:
Test 'M:Babil04_Mobil.Tests.ORMTests.Can_Insert_Multiple' failed: Cannot access a disposed object.
Object name: 'SqlCeResultSet'.
System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'SqlCeResultSet'.
at System.Data.SqlServerCe.SqlCeResultSet.CreateRecord()
MORMEngine.cs(162,0): at MORM.MORMEngine.Add[T](List`1 entities)
Tests'ORMTests.cs(187,0): at Babil04_Mobil.Tests.ORMTests.Can_Insert_Multiple()
调用引发异常的方法的单元测试:
[Test]
public void Can_Insert_Multiple()
{
MORMEngine engine = new MORMEngine(connectionString);
engine.OpenConnection();
List<TestInventory> inventories = new List<TestInventory>();
for (int i = 0; i < 10000; i++)
{
inventories.Add(new TestInventory
{
Code = "test" + i
});
}
Stopwatch watch = new Stopwatch();
watch.Start();
int rows = engine.Add<TestInventory>(inventories);
watch.Stop();
Console.WriteLine("Completed in {0} ms.", watch.ElapsedMilliseconds);
Assert.That(rows == 10000);
}
Expcetions表示SqlCeResultSet已经被释放。我既没有在对象上调用Dispose()
方法,也没有将其设置为 null
.为什么会被处理掉?为什么它在调试模式下运行良好,而在发布模式下运行不行?
任何想法将不胜感激。
根据我之前的评论 - 似乎在int rows = engine.Add<TestInventory>(inventories);
行之后的测试中没有对engine
的引用,并且Add<T>(List<T>)
中对engine
的唯一引用(通过隐式this
)是访问连接的行。在此行之后,没有对引擎的进一步引用,因此在发布模式下,它将有资格使用 GC。看起来(鉴于我的建议纠正了这个问题)可能在某处(在您的MORMEngine
或其他地方)有一个终结器导致连接在Add<>()
仍在运行时被处置。(在调试模式下,对象的生存期会延长,直到它们退出范围以简化调试,这可能是此问题仅在发布模式下出现的原因)
为了确保引擎在整个调用Add<>()
期间保持活动状态,以下方法似乎有效:
...
watch.Start();
int rows = engine.Add<TestInventory>(inventories);
GC.KeepAlive(engine); // Ensure that the engine remains alive until Add completes
watch.Stop();
...
或者最好使用 using()
语句显式处置engine
:
[Test]
public void Can_Insert_Multiple()
{
using (MORMEngine engine = new MORMEngine(connectionString))
{
engine.OpenConnection();
List<TestInventory> inventories = new List<TestInventory>();
for (int i = 0; i < 10000; i++)
{
inventories.Add(new TestInventory
{
Code = "test" + i
});
}
Stopwatch watch = new Stopwatch();
watch.Start();
int rows = engine.Add<TestInventory>(inventories);
watch.Stop();
Console.WriteLine("Completed in {0} ms.", watch.ElapsedMilliseconds);
Assert.That(rows == 10000);
}
}