funciones - instrucciones lambda c#
¿Por qué llamar a una expresión Python lambda desde C#no es seguro para subprocesos? (3)
Defino una expresión lambda libre de efectos secundarios (pura) en IronPython y la asigno a un delegado de C #. Al invocar al delegado de forma simultánea desde múltiples hilos, obtengo excepciones de tipo AccessViolationException , NullReferenceException y FatalEngineExecutionError .
La ocurrencia del error no es determinista y, en general, se necesitan varios millones de iteraciones para provocarlo, lo que para mí significa "condición de carrera". ¿Cómo puedo evitarlo?
Las excepciones solo se plantean cuando se ejecuta el proceso con x64 (x86 no falla) y fuera del depurador. El sistema de prueba es un Core I7 (8 hilos) en Windows 7, .NET Framework 4.0 e IronPython 2.7.1.
Aquí está el código mínimo para producir el error:
var engine = Python.CreateEngine();
double a = 1.0;
double b = 2.0;
while (true)
{
Func<double, double, double> calculate = engine.Execute("lambda a,b : a+b");
System.Threading.Tasks.Parallel.For(0, 1000, _ =>
{
for (int i = 0; i < 1000; i++) { calculate(a,b); }
});
Console.Write(".");
}
Mensaje de error:
FatalExecutionEngineError fue detectado
Mensaje: el tiempo de ejecución ha encontrado un error fatal. La dirección del error estaba en 0xf807829e, en el hilo 0x3da0. El código de error es 0xc0000005. Este error puede ser un error en el CLR o en las partes inseguras o no verificables del código de usuario. Las fuentes comunes de este error incluyen errores de clasificación de usuarios para COM-interop o PInvoke, que pueden dañar la pila.
Actualización: incluso si el motor se declara como local de subprocesos, se bloquea después de un tiempo:
var calculate = new ThreadLocal<Func<double, double, double>>(() => Python.CreateEngine().Execute("lambda a,b : a+b"));
¿Has intentado reemplazar el tipo de datos de 64 bits (doble) con un tipo de datos de 32 bits (flotante)? Sé que "una simple lectura o escritura en un campo de 32 bits o menos es siempre atómica", mientras que un campo de 64 bits puede tener problemas dependiendo del procesador y el código. Parece una posibilidad remota, pero vale la pena intentarlo.
Ejecutar código similar (ver a continuación) en IronScheme, no plantea tales problemas.
Lo único que se me ocurre es que engine.Execute("lambda a,b : a+b")
crea una función interpretada en lugar de una compilada. Combine eso con un posible intérprete inseguro, y kaboom.
double a = 1.0;
double b = 2.0;
while (true)
{
var calculate = "(lambda (a b) (+ a b))".Eval<Callable>();
System.Threading.Tasks.Parallel.For(0, 1000, _ =>
{
for (int i = 0; i < 1000; i++) { calculate.Call(a, b); }
});
Console.Write(".");
}
Esto es probablemente debido a una condición de carrera dentro de ScriptEngine. Tenga en cuenta que ScriptEngine.Execute devuelve una referencia a una función de Python en lugar de a un Func (se debe al comportamiento dinámico de C #, que puede tratar el resultado como un Func. No soy un experto en IronPython, pero miro la fuente de PythonFunction, hay no hay indicación alguna de que sea seguro para la rosca.