2015-02-05 12 views
12

Dire che ho una classe che deve eseguire l'inizializzazione async usando un metodo InitializeAsync(). Voglio assicurarmi che l'inizializzazione venga eseguita solo una volta. Se un altro thread chiama questo metodo mentre l'initalizzazione è già in corso, si "attende" fino a quando non viene restituita la prima chiamata.Applicare un metodo asincrono per essere chiamato una volta

Stavo pensando alla seguente implementazione (utilizzando SemaphoreSlim). Esiste un approccio migliore/più semplice?

public class MyService : IMyService 
{ 
    private readonly SemaphoreSlim mSemaphore = new SemaphoreSlim(1, 1); 
    private bool mIsInitialized; 

    public async Task InitializeAsync() 
    { 
     if (!mIsInitialized) 
     { 
      await mSemaphore.WaitAsync(); 

      if (!mIsInitialized) 
      { 
       await DoStuffOnlyOnceAsync(); 
       mIsInitialized = true; 
      } 

      mSemaphore.Release(); 
     } 
    } 

    private Task DoStuffOnlyOnceAsync() 
    { 
     return Task.Run(() => 
     { 
      Thread.Sleep(10000); 
     }); 
    } 
} 

Grazie!

Edit:

Dal momento che sto usando DI e questo servizio sarà iniettato, consumando come una risorsa "pigro" o utilizzando una fabbrica asincrona non funziona per me (anche se potrebbe essere grande in altri casi d'uso). Pertanto, l'inizializzazione asincrona dovrebbe essere incapsulata all'interno della classe e trasparente per i consumatori IMyService.

L'idea di avvolgere il codice di inizializzazione in un oggetto "fittizio" AsyncLazy<> farà il lavoro, anche se mi sembra un po 'innaturale.

+0

Utilizzare un 'Lazy' il cui metodo di inizializzazione fa ciò che si desidera. Ciò garantisce che il metodo di inizializzazione venga chiamato solo una volta –

+0

Se si utilizza questa soluzione, assicurarsi di chiamare "Rilascio" in un blocco finale. –

risposta

7

mi piacerebbe andare con AsyncLazy<T> (versione leggermente modificata):

public class AsyncLazy<T> : Lazy<Task<T>> 
{ 
    public AsyncLazy(Func<T> valueFactory) : 
     base(() => Task.Run(valueFactory)) { } 

    public AsyncLazy(Func<Task<T>> taskFactory) : 
     base(() => Task.Run(() => taskFactory()) { } 

    public TaskAwaiter<T> GetAwaiter() { return Value.GetAwaiter(); } 
} 

e consumarlo in questo modo:

private AsyncLazy<bool> asyncLazy = new AsyncLazy<bool>(async() => 
            { 
             await DoStuffOnlyOnceAsync() 
             return true; 
            }); 

Nota sto usando bool semplicemente perché non hai nessun tipo di ritorno da DoStuffOnlyOnceAsync.

Edit:

Stephan Cleary (ovviamente) ha anche un'implementazione di questo here.

+0

Questa è l'implementazione [AsyncLazy] di Stephen Toub (http://blogs.msdn.com/b/pfxteam/archive/2011/01/15/10116210.aspx) per .NET 4.0.Descrive perché è necessario farlo invece di chiamare semplicemente il metodo asincrono durante l'inizializzazione. –

+0

@PanagiotisKanavos lo so. Questo viene copiato dal suo post nel team PFX. Ho aggiunto il collegamento :) –

+0

Stephen Cleary aggiorna questo per .NET 4.5 e elimina lo unwrapping [qui] (http://blog.stephencleary.com/2012/08/asynchronous-lazy-initialization.html) –

6

Sì. Utilizzare Stephen Cleary's AsyncLazy (disponibile sul AsyncEx nuget):

private static readonly AsyncLazy<MyResource> myResource = new AsyncLazy<MyResource>(
    async() => 
    { 
     var ret = new MyResource(); 
     await ret.InitAsync(); 
     return ret; 
    } 
); 

public async Task UseResource() 
{ 
    MyResource resource = await myResource; 
    // ... 
} 

O il visual studio SDK's AsyncLazy se si preferisce un'implementazione Microsoft.

3

Ho un blog post that covers a few different options for doing "asynchronous constructors".

Normalmente, preferisco metodi di fabbrica asincroni, perché penso che siano più semplici e un po 'più sicuro:

public class MyService 
{ 
    private MyService() { } 

    public static async Task<MyService> CreateAsync() 
    { 
    var result = new MyService(); 
    result.Value = await ...; 
    return result; 
    } 
} 

AsyncLazy<T> è un modo perfetto per definire una risorsa condivisa asincrona (e può essere una migliore concettuale abbinare per un "servizio", a seconda di come viene utilizzato). L'unico vantaggio dell'approccio metodo factory asincrono è che non è possibile creare una versione non inizializzata di MyService.