2012-11-23 21 views
9

Korzystam z usługi asynchronicznej dla projektu Windows 8 i jest kilka asynchronicznych wywołań tej usługi, które powinny być wywoływane tylko jeden raz na raz.Czy zadanie może mieć wiele osób oczekujących?

public async Task CallThisOnlyOnce() 
{ 
     PropagateSomeEvents(); 

     await SomeOtherMethod(); 

     PropagateDifferentEvents(); 
} 

Ponieważ nie można upakować połączenia transmisji asynchronicznej w blokadzie rachunku, myślałem o użyciu wzorca AsyncLock, ale nie myślałem, że równie dobrze może spróbować czegoś takiego:

private Task _callThisOnlyOnce; 
public Task CallThisOnlyOnce() 
{ 
     if(_callThisOnlyOnce != null && _callThisOnlyOnce.IsCompleted) 
     _callThisOnlyOnce = null; 

     if(_callThisOnlyOnce == null) 
     _callThisOnlyOnce = CallThisOnlyOnceAsync(); 

     return _callThisOnlyOnce; 
} 

private async Task CallThisOnlyOnceAsync() 
{ 
     PropagateSomeEvents(); 

     await SomeOtherMethod(); 

     PropagateDifferentEvents(); 
} 

Dlatego byś kończy się wywołanie CallThisOnlyOnceAsync wykonywane tylko raz jednocześnie, a wiele osób oczekujących zahaczyło o to samo zadanie.

Czy jest to "prawidłowy" sposób postępowania, czy też istnieją pewne wady tego podejścia?

Odpowiedz

6

Zadanie może mieć wiele osób oczekujących. Jednakże, jak zauważył Damien, z proponowanym kodem panują poważne warunki wyścigowe.

Jeśli chcesz, aby kod wykonywany był przy każdym wywołaniu metody (ale nie jednocześnie), użyj AsyncLock. Jeśli chcesz, aby kod był wykonywany tylko raz, użyj AsyncLazy.

Proponowane przez Ciebie rozwiązanie próbuje połączyć wiele połączeń, ponownie wykonując kod, jeśli jeszcze nie jest uruchomiony. Jest to trudniejsze, a rozwiązanie zależy w dużej mierze od semantyki, której potrzebujesz. Oto jedna opcja:

private AsyncLock mutex = new AsyncLock(); 
private Task executing; 

public async Task CallThisOnlyOnceAsync() 
{ 
    Task action = null; 
    using (await mutex.LockAsync()) 
    { 
    if (executing == null) 
     executing = DoCallThisOnlyOnceAsync(); 
    action = executing; 
    } 

    await action; 
} 

private async Task DoCallThisOnlyOnceAsync() 
{ 
    PropagateSomeEvents(); 

    await SomeOtherMethod(); 

    PropagateDifferentEvents(); 

    using (await mutex.LockAsync()) 
    { 
    executing = null; 
    } 
} 

Jest również możliwe, aby to zrobić z Interlocked, ale że kod staje się brzydka.

P.S. Mam AsyncLock, AsyncLazy i inne async -jednostki pierwotne w moim AsyncEx library.

+0

Podobały mi się obie odpowiedzi, ale ponieważ dodałaś propozycję wdrożenia, wybrałam twoją. Ponadto próbowałem zainstalować bibliotekę za pomocą Nuget, ale nie udało się w moim projekcie Windows Store (nie można rozwiązać Microsoft.Bcl.Async). – UrbanEsc

+1

Spróbuj zaznaczyć pole wyboru "wstępne wydanie wstępne". Mój pakiet jest w wersji wstępnej (ale NuGet nie wykrywa tego poprawnie), a 'Microsoft.Bcl.Async' jest także w wersji wstępnej (którą NuGet wykrywa poprawnie). –

4

Ten kod wygląda na bardzo "rasistowski", jeśli może być zaangażowanych wiele wątków.

Jeden przykład (jestem pewien, że jest ich więcej). Zakładamy, że _callThisOnlyOnce jest obecnie null:

Thread 1               Thread 2 

public Task CallThisOnlyOnce() 
{ 
    if(_callThisOnlyOnce != null && _callThisOnlyOnce.IsCompleted) 
    _callThisOnlyOnce = null; 

    if(_callThisOnlyOnce == null) 
                    public Task CallThisOnlyOnce() 
                    { 
                    if(_callThisOnlyOnce != null && _callThisOnlyOnce.IsCompleted) 
                     _callThisOnlyOnce = null; 

                    if(_callThisOnlyOnce == null) 
                     _callThisOnlyOnce = CallThisOnlyOnceAsync(); 

                    return _callThisOnlyOnce; 
                    } 
    _callThisOnlyOnce = CallThisOnlyOnceAsync(); 

    return _callThisOnlyOnce; 
} 

Teraz masz 2 połączeń uruchomionych jednocześnie.

Co do wielu osób oczekujących, tak, możesz to zrobić. Jestem pewien, widziałem przykład kodu z MS gdzieś gdzie pokazano optymalizację, gdzie np. wynik Task.FromResult(0) jest przechowywany w statycznym elemencie członkowskim i zwracany za każdym razem, gdy funkcja chce zwrócić zero.

Niestety, nie udało mi się zlokalizować tego kodu.

Powiązane problemy