[c#] ¿Por qué no puedo usar el operador 'await' dentro del cuerpo de una instrucción de bloqueo?


Answers

Utilice el método SemaphoreSlim.WaitAsync .

 await mySemaphoreSlim.WaitAsync();
 try {
     await Stuff();
 } finally {
     mySemaphoreSlim.Release();
 }
Question

La palabra clave await en C # (.NET Async CTP) no está permitida dentro de una instrucción de bloqueo.

Desde MSDN :

Una expresión de espera no puede utilizarse en una función síncrona, en una expresión de consulta, en el bloque catch o finally de una instrucción de gestión de excepciones, en el bloque de una sentencia de bloqueo o en un contexto inseguro.

Supongo que esto es difícil o imposible para el equipo del compilador implementar por alguna razón.

Intenté trabajar 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.Salir dentro de ExitDisposable.Dispose parece bloquearse indefinidamente (la mayoría de las veces) provocando bloqueos mientras otros subprocesos intentan adquirir el bloqueo. Sospecho que la falta de fiabilidad de mi trabajo y la razón por la cual las declaraciones no están permitidas en la declaración de bloqueo están relacionadas de alguna manera.

¿Alguien sabe por qué no está permitido esperar dentro del cuerpo de una declaración de bloqueo?




Intenté usar un Monitor (código a continuación) que parece funcionar pero tiene un GOTCHA ... cuando tienes múltiples hilos dará ... System.Threading.SynchronizationLockException El método de sincronización de objetos fue llamado desde un bloque de código no sincronizado.

using System;
using System.Threading;
using System.Threading.Tasks;

namespace MyNamespace
{
    public class ThreadsafeFooModifier : 
    {
        private readonly object _lockObject;

        public async Task<FooResponse> ModifyFooAsync()
        {
            FooResponse result;
            Monitor.Enter(_lockObject);
            try
            {
                result = await SomeFunctionToModifyFooAsync();
            }
            finally
            {
                Monitor.Exit(_lockObject);
            }
            return result;
        }
    }
}

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; } }




Esto se refiere a blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx , http://winrtstoragehelper.codeplex.com/ , 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 todo sea bastante fácil, pero también presenta un escenario que rara vez se encontraba antes de que fuera tan fácil utilizar llamadas asíncronas: reentrada.

Esto es especialmente cierto para los manejadores de eventos, porque para muchos eventos no tiene ninguna pista sobre lo que sucede después de que regrese del controlador de eventos. Una cosa que podría ocurrir realmente es que el método asíncrono que está esperando en el primer controlador de eventos reciba una llamada de otro controlador de eventos que todavía está en el mismo hilo.

Este es un escenario real que encontré en una aplicación de la tienda de aplicaciones de Windows 8: Mi aplicación tiene dos cuadros: entrar y salir de un cuadro que quiero cargar / guardar algunos datos en el archivo / almacenamiento. Los eventos OnNavigatedTo / From se usan para guardar y cargar. El guardado y la carga se realizan mediante alguna función de utilidad asíncrona (como http://winrtstoragehelper.codeplex.com/ ). Al navegar desde el cuadro 1 al cuadro 2 o en la otra dirección, se llaman y esperan las operaciones de carga y seguridad asíncronas. Los controladores de eventos se vuelven asíncronos y devuelven void => no se pueden esperar.

Sin embargo, la primera operación de abrir archivo (permite dice: dentro de una función de guardar) de la utilidad también es asincrónica, por lo que la primera espera devuelve el control al marco, que luego 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 salvar, falla con una excepción ACCESSDENIED.

Una solución mínima para mí es asegurar el acceso al archivo mediante un uso y un AsyncLock.

private static readonly AsyncLock m_lock = new AsyncLock();
...

using (await m_lock.LockAsync())
{
    file = await folder.GetFileAsync(fileName);
    IRandomAccessStream readStream = await file.OpenAsync(FileAccessMode.Read);
    using (Stream inStream = Task.Run(() => readStream.AsStreamForRead()).Result)
    {
        return (T)serializer.Deserialize(inStream);
    }
}

Tenga en cuenta que su bloqueo básicamente bloquea toda la operación de archivos para la utilidad con solo un bloqueo, que es innecesariamente fuerte pero funciona bien para mi escenario.

Here está mi proyecto de prueba: una aplicación de 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 blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx .

También puedo sugerir este enlace: hanselman.com/blog/…




Stephen Taub ha implementado una solución a esta pregunta, vea Construyendo primitivas de coordinación asincrónica, 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 mostraré cómo usarlo:

/// <summary>
///     Demo class for reader/writer lock that supports async/await.
///     For source, see Stephen Taub's brilliant article, "Building Async Coordination
///     Primitives, Part 7: AsyncReaderWriterLock".
/// </summary>
public class AsyncReaderWriterLockDemo
{
    private readonly IAsyncReaderWriterLock _lock = new AsyncReaderWriterLock(); 

    public async void DemoCode()
    {           
        using(var releaser = await _lock.ReaderLockAsync()) 
        { 
            // Insert reads here.
            // Multiple readers can access the lock simultaneously.
        }

        using (var releaser = await _lock.WriterLockAsync())
        {
            // Insert writes here.
            // If a writer is in progress, then readers are blocked.
        }
    }
}

Si tiene un método que está integrado en .NET Framework, use SemaphoreSlim.WaitAsync lugar. No obtendrá un bloqueo de lector / escritor, pero obtendrá una implementación probada.




Links