2013-09-03 16 views
17

Potrzebuję napisać asynchroniczny kod, który zasadniczo próbuje wielokrotnie rozmawiać i inicjować bazę danych. Dość często pierwsza próba zakończy się niepowodzeniem, a więc wymaganie, aby ponowić próbę.Najlepsza asynchroniczna metoda

W dawnych czasach użyłbym wzór podobny do:

void WaitForItToWork() 
{ 
    bool succeeded = false; 
    while (!succeeded) 
    { 
     // do work 
     succeeded = outcome; // if it worked, mark as succeeded, else retry 
     Threading.Thread.Sleep(1000); // arbitrary sleep 
    } 
} 

Zdaję sobie sprawę, wiele zmian jakie zaszły niedawno NET w odniesieniu do asynchronicznych wzorów więc moje pytanie jest naprawdę to najlepszy metoda użycia lub czy warto eksplorować rzeczy async, a jeśli tak, w jaki sposób mogę wdrożyć ten wzór w async?

Aktualizacja

Właśnie w celu wyjaśnienia, chcę tarło tej pracy asynchronicznie tak, że metoda, która ikra to nie musi czekać na zakończenie procesu, jak to będzie zrodził się w konstruktora usługi tak konstruktor musi natychmiast wrócić.

Odpowiedz

25

Można byłaby ten fragment tak:

async Task<bool> WaitForItToWork() 
{ 
    bool succeeded = false; 
    while (!succeeded) 
    { 
     // do work 
     succeeded = outcome; // if it worked, make as succeeded, else retry 
     await Task.Delay(1000); // arbitrary delay 
    } 
    return succeeded; 
} 

Podobno jedyną korzyścią byłoby to daje to bardziej efektywne wykorzystanie puli wątków, ponieważ nie zawsze cały wątek, aby opóźnienie zdarzyć.

W zależności od sposobu uzyskania outcome, może być o wiele bardziej wydajnych sposobów wykonania tego zadania za pomocą async/await. Często zdarza się, że masz coś takiego, jak GetOutcomeAsync(), które spowodowałoby asynchroniczne wywołanie usługi sieci Web, bazy danych lub gniazda, w naturalny sposób, więc wystarczy wykonać var outcome = await GetOutcomeAsync().

Należy wziąć pod uwagę, że WaitForItToWork zostanie podzielony na części przez kompilator, a część z linii await będzie kontynuowana asynchronicznie. Here's być może najlepszym wytłumaczeniem, jak to zrobić wewnętrznie. Rzecz w tym, że zwykle w pewnym momencie kodu trzeba zsynchronizować wynik zadania asynchronicznego. Np .:

private void Form1_Load(object sender, EventArgs e) 
{ 
    Task<bool> task = WaitForItToWork(); 
    task.ContinueWith(_ => { 
     MessageBox.Show("WaitForItToWork done:" + task.Result.toString()); // true or false 
    }, TaskScheduler.FromCurrentSynchronizationContext()); 
} 

Mogłeś po prostu zrobić to:

private async void Form1_Load(object sender, EventArgs e) 
{ 
    bool result = await WaitForItToWork(); 
    MessageBox.Show("WaitForItToWork done:" + result.toString()); // true or false 
} 

To byłoby jednak zrobić Form1_Load metodę asynchroniczny też.

[UPDATE]

Poniżej moja próba zilustrować co async/await faktycznie robi w tej sprawie. Stworzyłem dwie wersje tej samej logiki, WaitForItToWorkAsync (używając async/await) i WaitForItToWorkAsyncTap (używając TAP pattern bez async/await). Wersja pierwsza jest dość trywialna, w przeciwieństwie do drugiej. Tak więc, podczas gdy async/await jest w dużej mierze syntaktycznym cukrem kompilatora, sprawia, że ​​kod asynchroniczny jest znacznie łatwiejszy do napisania i zrozumienia.

// fake outcome() method for testing 
bool outcome() { return new Random().Next(0, 99) > 50; } 

// with async/await 
async Task<bool> WaitForItToWorkAsync() 
{ 
    var succeeded = false; 
    while (!succeeded) 
    { 
     succeeded = outcome(); // if it worked, make as succeeded, else retry 
     await Task.Delay(1000); 
    } 
    return succeeded; 
} 

// without async/await 
Task<bool> WaitForItToWorkAsyncTap() 
{ 
    var context = TaskScheduler.FromCurrentSynchronizationContext(); 
    var tcs = new TaskCompletionSource<bool>(); 
    var succeeded = false; 
    Action closure = null; 

    closure = delegate 
    { 
     succeeded = outcome(); // if it worked, make as succeeded, else retry 
     Task.Delay(1000).ContinueWith(delegate 
     { 
      if (succeeded) 
       tcs.SetResult(succeeded); 
      else 
       closure(); 
     }, context); 
    }; 

    // start the task logic synchronously 
    // it could end synchronously too! (e.g, if we used 'Task.Delay(0)') 
    closure(); 

    return tcs.Task; 
} 

// start both tasks and handle the completion of each asynchronously 
private void StartWaitForItToWork() 
{ 
    WaitForItToWorkAsync().ContinueWith((t) => 
    { 
     MessageBox.Show("WaitForItToWorkAsync complete: " + t.Result.ToString()); 
    }, TaskScheduler.FromCurrentSynchronizationContext()); 

    WaitForItToWorkAsyncTap().ContinueWith((t) => 
    { 
     MessageBox.Show("WaitForItToWorkAsyncTap complete: " + t.Result.ToString()); 
    }, TaskScheduler.FromCurrentSynchronizationContext()); 
} 

// await for each tasks (StartWaitForItToWorkAsync itself is async) 
private async Task StartWaitForItToWorkAsync() 
{ 
    bool result = await WaitForItToWorkAsync(); 
    MessageBox.Show("WaitForItToWorkAsync complete: " + result.ToString()); 

    result = await WaitForItToWorkAsyncTap(); 
    MessageBox.Show("WaitForItToWorkAsyncTap complete: " + result.ToString()); 
} 

Kilka słów o gwintowania. Nie ma tu żadnych dodatkowych wątków jawnie utworzonych tutaj.Wewnętrznie implementacja Task.Delay() może wykorzystywać wątki puli (podejrzewam, że używają one Timer Queues), ale w tym konkretnym przykładzie (aplikacja WinForms) kontynuacja po await stanie się w tym samym wątku UI. W innych środowiskach wykonawczych (na przykład w aplikacji konsolowej) może być kontynuowany w innym wątku. IMO, this article autorstwa Stephena Cleary'a jest koniecznym do zrozumienia pojęciem gwintowania async/await.

+0

Czy można to nazwać po prostu wykonaniem 'this.WaitForItToWork();' - czy biblioteka asynchroniczna zajmie się wątkiem dla mnie? – Chris

+1

Nazwałbyś to jak 'poczekaj na tę funkcję.WaitForItToWork()', a cały łańcuch wywołań musi być refaktoryzowany, aby to wspierać ... Rozwiążę moją odpowiedź, aby dodać więcej informacji. – Noseratio

+0

@ Chris: musisz pamiętać, aby użyć słowa kluczowego "czekaj". Zasada kciuka: Zawsze "oczekuj" musi być połączona z funkcją "asynchroniczną". Tak więc powinieneś zrobić coś takiego, jak czekać na WaitForItToWork(); –

0

Jeśli zadanie jest asynchroniczny można spróbować z:

async Task WaitForItToWork() 
    { 
     await Task.Run(() => 
     { 
      bool succeeded = false; 
      while (!succeeded) 
      { 
       // do work 
       succeeded = outcome; // if it worked, make as succeeded, else retry 
       System.Threading.Thread.Sleep(1000); // arbitrary sleep 
      } 
     }); 
    } 

Zobacz http://msdn.microsoft.com/en-us/library/hh195051.aspx.

+1

Dokładnie tego chciałem uniknąć przy pomocy mojej odpowiedzi. Być może brakuje mi czegoś:] – Noseratio

+0

@Noseratio Metoda asynchroniczna powróci natychmiast po oczekiwaniu. Kompilator C# wykonuje dla ciebie całą synchronizację wątków. http://msdn.microsoft.com/en-us/library/vstudio/hh156528.aspx Co ważne; "_Aczekanie na wyrażenie nie blokuje wątku, w którym jest wykonywane, zamiast tego powoduje, że kompilator zapisuje resztę asynchronicznej metody jako kontynuację oczekującego zadania" – Gusdor

+0

Mam błąd. Chillax. Bez szkody dla poprawienia lub zwiększenia przejrzystości, nawet po fakcie. Redagowałem, aby uniknąć nieporozumień ze strony innych czytelników. Nie edytowałem, żebyś wyglądał głupio. To naruszałoby zasadę DRY./sickburn – Gusdor

0

Tak naprawdę nie potrzeba WaitItForWork metody, po prostu czekają na zadania inicjalizacji bazy danych:

async Task Run() 
{ 
    await InitializeDatabase(); 
    // Do what you need after database is initialized 
} 

async Task InitializeDatabase() 
{ 
    // Perform database initialization here 
} 

Jeśli masz kilka fragmentów kodu, które wzywają do WaitForItToWork następnie trzeba owinąć inicjalizacji bazy danych i do Task czekają go we wszystkich pracowników, na przykład:

readonly Task _initializeDatabaseTask = InitializeDatabase(); 

async Task Worker1() 
{ 
    await _initializeDatabaseTask; 
    // Do what you need after database is initialized 
} 

async Task Worker2() 
{ 
    await _initializeDatabaseTask; 
    // Do what you need after database is initialized 
} 

static async Task InitializeDatabase() 
{ 
    // Initialize your database here 
} 
0

Wystarczy podać inne rozwiązanie

public static void WaitForCondition(Func<bool> predict) 
    { 
     Task.Delay(TimeSpan.FromMilliseconds(1000)).ContinueWith(_ => 
     { 
      var result = predict(); 
      // the condition result is false, and we need to wait again. 
      if (result == false) 
      { 
       WaitForCondition(predict); 
      } 
     }); 
    } 
Powiązane problemy