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




4 Answers

Utilice el método SemaphoreSlim.WaitAsync .

 await mySemaphoreSlim.WaitAsync();
 try {
     await Stuff();
 } finally {
     mySemaphoreSlim.Release();
 }
c# .net async-await

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

Desde MSDN :

Una expresión de espera no puede usarse en una función síncrona, en una expresión de consulta, en el retén o en el último bloque de una declaración de manejo de excepciones, en el bloque de una sentencia de bloqueo o en un contexto inseguro.

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

Intenté un trabajo alrededor con la declaración de uso:

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 bloquear indefinidamente (la mayoría de las veces) causando interbloqueos cuando otros subprocesos intentan adquirir el bloqueo. Sospecho que la falta de fiabilidad de mi trabajo y la razón por la que las declaraciones de espera no están permitidas en la declaración de bloqueo están relacionadas de alguna manera.

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




Esto se refiere a 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 en esto:

La función de lenguaje async / await 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 usar llamadas asíncronas: la reentrada.

Esto es especialmente cierto para los controladores de eventos, ya que para muchos eventos no tiene idea de lo que está sucediendo después de regresar del controlador de eventos. Una cosa que realmente podría suceder es que el método asíncrono que está esperando en el primer controlador de eventos, recibe una llamada desde otro controlador de eventos que aún se encuentra en el mismo hilo.

Este es un escenario real que encontré en una aplicación de Windows 8 App Store: mi aplicación tiene dos marcos: entrando y saliendo de un marco que quiero cargar / guardar algunos datos en un archivo / almacenamiento. Los eventos OnNavigatedTo / From se utilizan 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 llama y se espera la carga asíncrona y las operaciones seguras. Los manejadores de eventos se vuelven asíncronos devolviendo el vacío => no se pueden esperar.

Sin embargo, la primera operación de apertura de archivo (digamos: dentro de una función de guardado) de la utilidad también es asíncrona, por lo que la primera espera devuelve el control al marco, que 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.

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 todas las operaciones de archivo para la utilidad con un solo bloqueo, lo cual es innecesariamente sólido pero funciona bien para mi escenario.

Here 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 el AsyncLock de Stephen Toub blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx .

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




Hmm, parece feo, parece funcionar.

static class Async
{
    public static Task<IDisposable> Lock(object obj)
    {
        return TaskEx.Run(() =>
            {
                var resetEvent = ResetEventFor(obj);

                resetEvent.WaitOne();
                resetEvent.Reset();

                return new ExitDisposable(obj) as IDisposable;
            });
    }

    private static readonly IDictionary<object, WeakReference> ResetEventMap =
        new Dictionary<object, WeakReference>();

    private static ManualResetEvent ResetEventFor(object @lock)
    {
        if (!ResetEventMap.ContainsKey(@lock) ||
            !ResetEventMap[@lock].IsAlive)
        {
            ResetEventMap[@lock] =
                new WeakReference(new ManualResetEvent(true));
        }

        return ResetEventMap[@lock].Target as ManualResetEvent;
    }

    private static void CleanUp()
    {
        ResetEventMap.Where(kv => !kv.Value.IsAlive)
                     .ToList()
                     .ForEach(kv => ResetEventMap.Remove(kv));
    }

    private class ExitDisposable : IDisposable
    {
        private readonly object _lock;

        public ExitDisposable(object @lock)
        {
            _lock = @lock;
        }

        public void Dispose()
        {
            ResetEventFor(_lock).Set();
        }

        ~ExitDisposable()
        {
            CleanUp();
        }
    }
}



Intenté usar un Monitor (código a continuación) que parece funcionar pero tiene un GOTCHA ... cuando tiene varios subprocesos le dará ... System.Threading.SynchronizationLockException Se llamó al método de sincronización de objetos 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; } }




Related