2012-09-21 14 views
12

Tengo una Singleton Class que carga algunos datos sobre su construcción. El problema es que cargar estos datos requiere llamar a los métodos async, pero el constructor no puede ser async.Singleton Class que requiere alguna llamada asincrónica

En otras palabras, mi clase ha siguiente estructura:

public class Singleton 
{ 
    private static Singleton instance; 

    private Singleton() 
    { 
     LoadData(); 
    } 

    public static Singleton Instance 
    { 
     get 
     { 
     if (instance == null) 
     { 
      instance = new Singleton(); 
     } 
     return instance; 
     } 
    } 
} 

LoadData() es una función async que exige mucha async funciones, así como la inicialización. ¿Cómo puedo llamar al LoadData() correctamente para que todo se inicialice correctamente?

+2

Usando Lazy debe hacer lo que quiera. –

+0

también puede llamar a un método asíncrono desde un constructor. – DarthVader

+0

Proporcione más contexto sobre lo que quiere que suceda. No está claro en este momento. –

Respuesta

10

El problema es que cargar estos datos requiere llamar a los métodos asíncronos, pero el constructor no puede ser asincrónico.

Aunque no se puede hacer que el constructor sí asíncrono, usted puede llamadas métodos asíncronos desde dentro del constructor. Simplemente no obtendrá los resultados de inmediato.

Siempre que los métodos asincrónicos vuelven Task o Task<T>, siempre se puede utilizar una continuación en la tarea de establecer sus datos dentro de la clase una vez que la operación asincrónica, o simplemente bloquear en los resultados, en función de lo que tiene más sentido en tu escenario Sin conocer los requisitos para la construcción de este objeto, es difícil saber qué es apropiado en este escenario.


Editar:

Una opción, teniendo en cuenta los objetivos mencionados anteriormente, sería la de cambiar su declaración Singleton por lo que el método para recuperar la Instance era un método, no una propiedad. Esto permitiría que para que sea asíncrono:

public class Singleton 
{ 
    private static Singleton instance; 

    private Singleton() 
    { 
      // Don't load the data here - will be called separately 
    } 

    public static async Task<Singleton> GetInstance() 
    { 
     if (instance == null) 
     { 
      instance = new Singleton(); 
      await instance.LoadData(); 
     } 

     return instance; 
    } 
} 

Esto le permitiría utilizar await en la llamada para recuperar realmente la instancia. Lo bueno de esto es que deja muy claro que está llamando a una operación asincrónica, y obtendrá un manejo adecuado de los resultados, ya que el resultado volverá como cualquier otro método asíncrono.

Tenga en cuenta, sin embargo, que esto no es seguro para subprocesos (aunque tampoco el original), por lo que si va a utilizar este Singleton de varios subprocesos, puede tener que replantearse el diseño general.

La otra opción sería hacer que su clase Singleton no cargue automáticamente los datos. Haga que los métodos que recuperan los datos de la clase sean asincrónicos, en su lugar. Esto proporciona algunas ventajas reales, ya que el uso es probablemente un poco más estándar, y puede admitir llamadas de múltiples hilos de una manera más sencilla (ya que puede controlar el proceso de carga de datos) de lo que sería capaz de manejarlo haciendo que el acceso de la instancia de clase asincrónica.

+0

gracias, ¿me pueden dar un ejemplo simple, por favor? – MBZ

+0

@MBZ Editado para mostrar una opción. –

+0

Si usa el doble-control-bloqueo, va a tener un problema, ya que no puede usar esperar dentro de lock() –

4

Se puede utilizar para este asynchronous lazy initialization:

public class Singleton 
{ 
    private static readonly AsyncLazy<Singleton> instance = 
     new AsyncLazy<Singleton>(CreateAndLoadData); 

    private Singleton() 
    { 
    } 

    // This method could also be an async lambda passed to the AsyncLazy constructor. 
    private static async Task<Singleton> CreateAndLoadData() 
    { 
    var ret = new Singleton(); 
    await ret.LoadDataAsync(); 
    return ret; 
    } 

    public static AsyncLazy<Singleton> Instance 
    { 
    get { return instance; } 
    } 
} 

Y entonces se puede utilizar de esta manera:

Singleton singleton = await Singleton.Instance; 

Una ventaja de utilizar AsyncLazy<T> es que es multi-hilo. Sin embargo, tenga en cuenta que siempre ejecuta su delegado en un subproceso de grupo de subprocesos.

1

Bueno, no tiene mucho sentido que desee iniciar de forma asincrónica un singleton. Si simplemente desea llamar a un método que devuelve tarea en su inicialización, sólo tiene que hacer:

var task = MyAsyncMethod(); 
task.Wait(); 
return task.Result; 

Sin la necesidad de hacer que el método async.

Pero, si lo que queremos es que el valor singleton a ser una tarea, se puede utilizar como tal Lazy:

Lazy<Task<int>> l = new Lazy<Task<int>>(async() => { int i = await calculateNumber(); return i; }); 

Además, Lazy<T> es el método preferido para aplicar "únicos". Singleton clases son difíciles de conseguir la derecha (o difícil de mantener derecha) ...

6

La solución para un producto único flujos seguros, asíncrono en realidad es muy simple, si sólo dejamos que los mecanismos internos de la clase Task funciona para nosotros!

Entonces, ¿cómo funciona un Task? Supongamos que tiene una instancia de Task<T> y await una vez. Ahora la tarea se ejecuta y se produce y le devuelve un valor de T. ¿Qué sucede si await la misma tareainstancia nuevamente? En este caso, la tarea simplemente devuelve el valor producido previamente de forma completamente sincrónica.

¿Y qué ocurre si await la misma instancia de tarea simultáneamente de múltiples hilos (donde normalmente se obtendría una condición de carrera)? Bien, el primero (dado que allí será el que llegue primero) ejecutará el código de la tarea mientras que los otros esperarán a que se procese el resultado. Luego, cuando se haya producido el resultado, todos los await finalizarán (virtualmente) simultáneamente y devolverán el valor.

Así que la solución para un producto único async que es seguro para subprocesos es en realidad muy simple:

public class Singleton 
{ 
    private static readonly Task<Singleton> _getInstanceTask = CreateSingleton(); 

    public static Task<Singleton> Instance 
    { 
     get { return _getInstanceTask; } 
    } 

    private Singleton(SomeData someData) 
    { 
     SomeData = someData; 
    } 

    public SomeData SomeData { get; private set; } 

    private static async Task<Singleton> CreateSingleton() 
    { 
     SomeData someData = await LoadData(); 
     return new Singleton(someData); 
    } 
} 

Ahora se puede acceder al producto único de esta manera:

Singleton mySingleton = await Singleton.Instance; 

o

Singleton mySingleton = Singleton.Instance.Result; 

o

SomeData mySingletonData = (await Singleton.Instance).SomeData; 

o

SomeData mySingletonData = Singleton.Instance.Result.SomeData; 

Lee más aquí: Async singleton initialization

+0

Solución muy elegante. Increíblemente simple :) – TreeAndLeaf

Cuestiones relacionadas