Aquí está el código:IAsyncResult.AsyncWaitHandle.WaitOne() finaliza por delante de devolución de llamada
class LongOp
{
//The delegate
Action longOpDelegate = LongOp.DoLongOp;
//The result
string longOpResult = null;
//The Main Method
public string CallLongOp()
{
//Call the asynchronous operation
IAsyncResult result = longOpDelegate.BeginInvoke(Callback, null);
//Wait for it to complete
result.AsyncWaitHandle.WaitOne();
//return result saved in Callback
return longOpResult;
}
//The long operation
static void DoLongOp()
{
Thread.Sleep(5000);
}
//The Callback
void Callback(IAsyncResult result)
{
longOpResult = "Completed";
this.longOpDelegate.EndInvoke(result);
}
}
Aquí es el caso de prueba:
[TestMethod]
public void TestBeginInvoke()
{
var longOp = new LongOp();
var result = longOp.CallLongOp();
//This can fail
Assert.IsNotNull(result);
}
Si esto se ejecute el caso de prueba puede fallar. ¿Por qué exactamente?
Hay muy poca documentación sobre cómo funciona delegate.BeginInvoke. ¿Alguien tiene alguna idea que les gustaría compartir?
Actualización Esta es una sutil condición de carrera que no está bien documentada en MSDN ni en ningún otro lado. El problema, como se explica en la respuesta aceptada, es que cuando la operación finaliza, se señala el Identificador de Espera y luego se ejecuta la Devolución de Llamada. La señal libera el hilo principal en espera y ahora la ejecución de devolución de llamada entra en la "carrera". Jeffry Richter's suggested implementation muestra lo que sucede detrás de las escenas:
// If the event exists, set it
if (m_AsyncWaitHandle != null) m_AsyncWaitHandle.Set();
// If a callback method was set, call it
if (m_AsyncCallback != null) m_AsyncCallback(this);
Para una solución refieren a la respuesta de Ben Voigt. Esa implementación no implica la sobrecarga adicional de un segundo identificador de espera.
Elimine la devolución de llamada y vuelva a intentarlo. – jgauffin
@jgauffin, si nota que la pregunta no es "¿Cómo hago para que funcione?" Claramente este es un ejemplo artificial. –
Su pregunta es: "Si esto se ejecuta, el caso de prueba puede fallar. ¿Por qué exactamente?". Yo * sí * respondí eso. Porque intenta mezclar dos formas muy diferentes de manejar una operación asincrónica. – jgauffin