La palabra clave de espera en C # (.NET Async CTP) no está permitida desde una declaración de bloqueo.
De MSDN :
Una expresión en espera no se puede utilizar en una función síncrona, en una expresión de consulta, en el bloque catch o finalmente de una declaración de manejo de excepciones, en el bloque de una declaración de bloqueo o en un contexto inseguro.
Supongo que esto es difícil o imposible de implementar por el equipo del compilador por alguna razón.
Intenté solucionar el problema con la instrucción using:
class Async
{
public static async Task<IDisposable> Lock(object obj)
{
while (!Monitor.TryEnter(obj))
await TaskEx.Yield();
return new ExitDisposable(obj);
}
private class ExitDisposable : IDisposable
{
private readonly object obj;
public ExitDisposable(object obj) { this.obj = obj; }
public void Dispose() { Monitor.Exit(this.obj); }
}
}
// example usage
using (await Async.Lock(padlock))
{
await SomethingAsync();
}
Sin embargo, esto no funciona como se esperaba. La llamada a Monitor.Exit dentro de ExitDisposable.Dispose parece bloquearse indefinidamente (la mayoría de las veces) causando puntos muertos a medida que otros hilos intentan adquirir el bloqueo. Sospecho que la falta de confiabilidad de mi trabajo y la razón por la cual las declaraciones no están permitidas en la declaración de bloqueo están de alguna manera relacionadas.
¿Alguien sabe por qué esperar no está permitido dentro del cuerpo de una declaración de bloqueo?
fuente
Respuestas:
No, no es nada difícil ni imposible de implementar; el hecho de que lo haya implementado usted mismo es un testimonio de ese hecho. Más bien, es una idea increíblemente mala y, por lo tanto, no lo permitimos, para protegerlo de cometer este error.
Correcto, has descubierto por qué lo hicimos ilegal. Aguardar dentro de una cerradura es una receta para producir puntos muertos.
Estoy seguro de que puede ver por qué: el código arbitrario se ejecuta entre el momento en que la espera devuelve el control a la persona que llama y se reanuda el método . Ese código arbitrario podría estar eliminando bloqueos que producen inversiones de orden de bloqueo y, por lo tanto, puntos muertos.
Peor aún, el código podría reanudarse en otro subproceso (en escenarios avanzados; normalmente se retoma el subproceso que esperaba, pero no necesariamente), en cuyo caso el desbloqueo estaría desbloqueando un bloqueo en un subproceso diferente al que tomó fuera de la cerradura. ¿Es eso una buena idea? No.
Observo que también es una "peor práctica" hacer un
yield return
inside alock
, por la misma razón. Es legal hacerlo, pero desearía que lo hubiéramos hecho ilegal. No vamos a cometer el mismo error para "esperar".fuente
SemaphoreSlim.WaitAsync
clase se agregó al .NET Framework mucho después de que publicaste esta respuesta, creo que podemos asumir con seguridad que es posible ahora. Independientemente de esto, sus comentarios sobre la dificultad de implementar tal construcción siguen siendo completamente válidos.SemaphoreSlim.WaitAsync
Método de usofuente
Stuff
No veo ninguna forma de evitarlo ...mySemaphoreSlim = new SemaphoreSlim(1, 1)
para que funcionelock(...)
?Básicamente, sería lo incorrecto hacer.
Hay dos maneras en que esto podría ser implementado:
Mantenga el bloqueo, soltándolo solo al final del bloque .
Esta es una muy mala idea, ya que no sabes cuánto tiempo llevará la operación asincrónica. Solo debe mantener las cerraduras durante un tiempo mínimo . También es potencialmente imposible, ya que un subproceso posee un bloqueo, no un método, y es posible que ni siquiera ejecute el resto del método asincrónico en el mismo subproceso (dependiendo del programador de tareas).
Suelte el bloqueo en espera y vuelva a adquirirlo cuando regrese el espera
Esto viola el principio de la menor OMI de asombro, donde el método asincrónico debe comportarse lo más parecido posible al código sincrónico equivalente, a menos que lo use
Monitor.Wait
en un bloque de bloqueo, espera poseer la cerradura por la duración del bloque.Entonces, básicamente, hay dos requisitos en competencia aquí: no debe intentar hacer el primero aquí, y si desea tomar el segundo enfoque, puede hacer que el código sea mucho más claro al tener dos bloques de bloqueo separados separados por la expresión de espera:
Entonces, al prohibirle que espere en el bloque de bloqueo, el lenguaje lo obliga a pensar en lo que realmente quiere hacer y hace que esa elección sea más clara en el código que escribe.
fuente
SemaphoreSlim.WaitAsync
clase se agregó al .NET Framework mucho después de que publicaste esta respuesta, creo que podemos asumir con seguridad que es posible ahora. Independientemente de esto, sus comentarios sobre la dificultad de implementar tal construcción siguen siendo completamente válidos.Esto es solo una extensión de esta respuesta .
Uso:
fuente
try
bloque, si ocurre una excepciónWaitAsync
ytry
el semáforo nunca se liberará (punto muerto). Por otro lado, mover laWaitAsync
llamada altry
bloque introducirá otro problema, cuando el semáforo puede liberarse sin que se adquiera un bloqueo. Vea el hilo relacionado donde se explicó este problema: stackoverflow.com/a/61806749/7889645Esto se refiere a http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx , http://winrtstoragehelper.codeplex.com/ , la tienda de aplicaciones de Windows 8 y .net 4.5
Aquí está mi ángulo sobre esto:
La función de lenguaje asíncrono / espera hace que muchas cosas sean bastante fáciles, pero también presenta un escenario que rara vez se encontraba antes de que fuera tan fácil de usar las llamadas asíncronas: reentrada.
Esto es especialmente cierto para los controladores de eventos, porque para muchos eventos no tienes idea de lo que está sucediendo después de regresar del controlador de eventos. Una cosa que podría suceder realmente es que el método asíncrono que está esperando en el primer controlador de eventos se llama desde otro controlador de eventos que todavía está en el mismo hilo.
Aquí hay un escenario real que encontré en una aplicación de la tienda de aplicaciones de Windows 8: Mi aplicación tiene dos marcos: entrando y saliendo de un marco que quiero cargar / guardar algunos datos en el archivo / almacenamiento. Los eventos OnNavigatedTo / From se utilizan para guardar y cargar. El guardado y la carga se realiza mediante alguna función de utilidad asíncrona (como http://winrtstoragehelper.codeplex.com/ ). Cuando se navega desde el cuadro 1 al cuadro 2 o en la otra dirección, se llama y espera la carga asíncrona y las operaciones seguras. Los controladores de eventos se vuelven asíncronos y devuelven void => no se pueden esperar.
Sin embargo, la primera operación de apertura de archivo (digamos: dentro de una función de guardar) de la utilidad también es asíncrona, por lo que la primera espera devuelve el control al marco, que en algún momento más tarde llama a la otra utilidad (carga) a través del segundo controlador de eventos. La carga ahora intenta abrir el mismo archivo y si el archivo ya está abierto para la operación de guardar, falla con una excepción ACCESSDENIED.
Una solución mínima para mí es asegurar el acceso a los archivos mediante un uso y un AsyncLock.
Tenga en cuenta que su bloqueo básicamente bloquea todas las operaciones de archivo para la utilidad con solo un bloqueo, que es innecesariamente fuerte pero funciona bien para mi situación.
Aquí está mi proyecto de prueba: una aplicación de la tienda de aplicaciones de Windows 8 con algunas llamadas de prueba para la versión original de http://winrtstoragehelper.codeplex.com/ y mi versión modificada que usa AsyncLock de Stephen Toub http: //blogs.msdn. com / b / pfxteam / archive / 2012/02/12 / 10266988.aspx .
¿Puedo sugerir también este enlace: http://www.hanselman.com/blog/ComparingTwoTechniquesInNETAsynchronousCoordinationPrimitives.aspx
fuente
Stephen Taub ha implementado una solución a esta pregunta, consulte Creación de primitivas de coordinación asíncrona, Parte 7: AsyncReaderWriterLock .
Stephen Taub es muy apreciado en la industria, por lo que todo lo que escribe es probable que sea sólido.
No reproduciré el código que publicó en su blog, pero le mostraré cómo usarlo:
Si desea un método integrado en .NET Framework, use
SemaphoreSlim.WaitAsync
en su lugar. No obtendrá un bloqueo de lector / escritor, pero obtendrá una implementación probada.fuente
SemaphoreSlim.WaitAsync
está en el marco .NET. Todo lo que hace este código es agregar un concepto de bloqueo de lector / escritor.Hmm, se ve feo, parece funcionar.
fuente
Intenté usar un Monitor (código a continuación) que parece funcionar pero tiene un GOTCHA ... cuando tiene varios subprocesos, dará ... System.Threading.SynchronizationLockException El método de sincronización de objetos se llamó desde un bloque de código no sincronizado.
Antes de esto, simplemente estaba haciendo esto, pero estaba en un controlador ASP.NET, por lo que resultó en un punto muerto.
public async Task<FooResponse> ModifyFooAsync() { lock(lockObject) { return SomeFunctionToModifyFooAsync.Result; } }
fuente