W poprzednim poście pokazałem jak korzystać z funkcji FromAsyncPattern na przykładzie usługi sieciowej. Dzisiaj zaprezentuję kilka dodatkowych funkcji. Najpierw zdefiniujmy co chcemy uzyskać:
- Użytkownik może wpisać szukaną frazę w pole edycyjne.
- Usługa sieciowa ma za zadanie wyszukanie fraz wpisanych w pole zdefiniowane w punkcie 1.
- Wyłącznie frazy dłuższe niż 3 znaki mają być przetwarzane.
- Jeśli użytkownik wpisze dwa razy tą samą frazę to tylko pierwsza ma zostać wysłana do usługi (optymalizacja).
- Zdarzenie TextChanged jest wywoływane dla każdego wpisanego znaku. Z tego względu, poprzednie rozwiązanie wysyłało dużo niepotrzebnych zapytań. W celu optymalizacji wprowadzimy opóźnienie takie jakie jest np. w Intellisense – propozycje nie są wyświetlane za każdym razem gdy jest wpisywany pojedynczy znak ale dopiero gdy użytkownik chwilę odczeka.
Klasyczne rozwiązanie problemu, bez użycia RX mogłoby wyglądać następująco:
public partial class MainWindow : Window { private string _previousText; private DateTime _lastRead; private const long MinimalTimeBetweenRequests = 1000*5; public MainWindow() { InitializeComponent(); } private void TextBoxTextChanged(object sender, TextChangedEventArgs e) { TimeSpan timeDiff = DateTime.Now - _lastRead; if (termTextBox.Text.Length >= 3 && _previousText != termTextBox.Text && timeDiff.TotalMilliseconds > MinimalTimeBetweenRequests) { DictServiceSoapClient client = new DictServiceSoapClient("DictServiceSoap"); _lastRead = DateTime.Now; client.BeginMatchInDict("wn", termTextBox.Text, "prefix", SearchCallback, client); } _previousText = termTextBox.Text; } private void SearchCallback(IAsyncResult result) { var client = (DictServiceSoapClient) result.AsyncState; DictionaryWord[] words = client.EndMatchInDict(result); Dispatcher.Invoke(new Action(() => UpdateResults(words))); } private void UpdateResults(IEnumerable<DictionaryWord> words) { StringBuilder resultsBuilder = new StringBuilder(); foreach (var word in words) { resultsBuilder.Append(word.Word); resultsBuilder.AppendLine(); } results.Text = resultsBuilder.ToString(); } }
Szczególnie nie lubię callback’ów i definiowania pól w klasie, które naprawdę wykorzystywane są tylko w jednym miejscu. Należy dążyć do sytuacji gdzie jest jak najmniej pól – upraszcza to czytanie kodu. Jeśli to tylko możliwe lepiej korzystać z zasobów lokalnych.
RX znacząco uprości powyższy problem. Najpierw warto jednak przyjrzeć się następującym metodom:
- Throttle – tłumi przetwarzanie danych. Dzięki temu łatwo zrealizować wymaganie numer 5. Wystarczy wywołać tą metodę z odpowiednim parametrem (czas) a RX zajmie się resztą.
-
DistinctUntilChanged – zwraca wyłącznie jednorazowe, unikatowe wartości. Jeśli zdarzenie TextChanged generuje “Piotr”,”Paweł’,”Paweł na wyjściu pojawi się “Piotr”,”Paweł”. Z kolei jeśli zdarzenia dostarczają “Piotr”,”Paweł”,”Piotr” na wyjściu ukażę się identyczna sekwencja. Z tego względu DistinctUntilChanged nie jest tym samym co Distinct.
Zatem rozwiązanie za pomocą RX może wyglądać następująco:
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); DictServiceSoapClient client = new DictServiceSoapClient("DictServiceSoap"); var dictionarySource = Observable.FromAsyncPattern<string, string, string, DictionaryWord[]>(client.BeginMatchInDict, client.EndMatchInDict); var inputSource = Observable.FromEventPattern<TextChangedEventArgs>(termTextBox, "TextChanged").Select(i => ((TextBox) i.Sender) .Text); inputSource.Throttle(TimeSpan.FromSeconds(1)). DistinctUntilChanged(). Where(input => input.Length >= 3). SelectMany(input => dictionarySource("wn", input, "prefix")). ObserveOn(SynchronizationContext.Current). Subscribe(UpdateResults, () => MessageBox.Show("Completed")); } private void UpdateResults(IEnumerable<DictionaryWord> words) { StringBuilder resultsBuilder = new StringBuilder(); foreach (var word in words) { resultsBuilder.Append(word.Word); resultsBuilder.AppendLine(); } results.Text = resultsBuilder.ToString(); } }
Dzięki RX programista może opisywać swoją intencję za pomocą jednego zdania. Taki kod jest czytelniejszy ponieważ nie trzeba skakać z jednego miejsca do drugiego – wszystko jest zawarte w tym zdaniu.