Udostępnij za pośrednictwem


Korzystanie z usługi Schedulers

Harmonogram kontroluje, kiedy subskrypcja rozpoczyna się i kiedy są publikowane powiadomienia. Składa się z trzech składników. Jest to najpierw struktura danych. Po zaplanowaniu ukończenia zadań są one umieszczane w harmonogramie kolejkowania na podstawie priorytetu lub innych kryteriów. Oferuje również kontekst wykonywania, który określa miejsce wykonania zadania (np. w puli wątków, bieżący wątek lub w innej domenie aplikacji). Wreszcie, ma zegar, który zapewnia pojęcie czasu dla siebie (przez Now uzyskanie dostępu do właściwości harmonogramu). Zadania zaplanowane w określonym harmonogramie będą zgodne z czasem oznaczonym tylko przez ten zegar.

Harmonogramy przedstawiają również pojęcie czasu wirtualnego (oznaczonego typem VirtualScheduler), który nie jest skorelowany z czasem rzeczywistym używanym w naszym codziennym życiu. Na przykład sekwencja, która ma potrwać 100 lat, może zostać zaplanowana na ukończenie w czasie wirtualnym w ciągu zaledwie 5 minut. Zostanie to omówione w temacie Testowanie i debugowanie obserwowanych sekwencji .

Typy harmonogramu

Różne typy harmonogramu udostępniane przez program Rx wszystkie implementują interfejs IScheduler . Każdy z nich można utworzyć i zwrócić przy użyciu właściwości statycznych typu Scheduler. Element ImmediateScheduler (przez uzyskanie dostępu do statycznej właściwości natychmiastowej) natychmiast uruchomi określoną akcję. Element CurrentThreadScheduler (przez uzyskanie dostępu do statycznej właściwości CurrentThread) zaplanuje wykonywanie akcji w wątku, który tworzy oryginalne wywołanie. Akcja nie jest wykonywana natychmiast, ale jest umieszczana w kolejce i wykonywana tylko po zakończeniu bieżącej akcji. Dyspozytor DispatcherScheduler (przez uzyskanie dostępu do właściwości statycznego dyspozytora) będzie planować akcje w bieżącym dyspozytorze, co jest korzystne dla deweloperów programu Silverlight korzystających z języka Rx. Określone akcje są następnie delegowane do metody Dispatcher.BeginInvoke() w programie Silverlight. NewThreadScheduler (przez uzyskanie dostępu do statycznej właściwości NewThread ) planuje akcje w nowym wątku i jest optymalne do planowania długotrwałych lub blokujących akcji. Pula zadańScheduler (przez uzyskanie dostępu do właściwości statycznej puli zadań ) planuje akcje w określonej fabryce zadań. ThreadPoolScheduler (przez uzyskanie dostępu do właściwości static ThreadPool ) planuje akcje w puli wątków. Oba harmonogramy puli są zoptymalizowane pod kątem krótkich akcji.

Korzystanie z usługi Schedulers

Być może w kodzie Rx użyto już harmonogramów bez jawnego podania typu harmonogramów do użycia. Dzieje się tak, ponieważ wszystkie operatory, które obsługują współbieżność, mają wiele przeciążeń. Jeśli nie używasz przeciążenia, które przyjmuje harmonogram jako argument, Rx wybierze domyślny harmonogram przy użyciu zasady najmniej współbieżności. Oznacza to, że harmonogram, który wprowadza najmniejszą ilość współbieżności, która spełnia potrzeby operatora, jest wybierana.  Na przykład w przypadku operatorów zwracających możliwość obserwowania z skończonym i niewielką liczbą komunikatów funkcja Rx wywołuje natychmiastowe wywołania.  W przypadku operatorów zwracających potencjalnie dużą lub nieskończoną liczbę komunikatów jest wywoływana funkcja CurrentThread . W przypadku operatorów korzystających z czasomierzy używana jest pula wątków .

Ponieważ język Rx używa najmniej współbieżnego harmonogramu, możesz wybrać inny harmonogram, jeśli chcesz wprowadzić współbieżność do celu wydajności lub gdy masz problem z koligacją wątku.  Przykładem poprzedniego jest to, że jeśli nie chcesz blokować określonego wątku, w tym przypadku należy użyć puli wątków.  Przykładem tego ostatniego jest to, że gdy czasomierz ma być uruchomiony w interfejsie użytkownika, w tym przypadku należy użyć dyspozytora. Aby określić określony harmonogram, można użyć tych przeciążeń operatorów, które przyjmują harmonogram, np. Timer(TimeSpan.FromSeconds(10), Scheduler.DispatcherScheduler()).

W poniższym przykładzie sekwencja z możliwością obserwacji źródła generuje wartości w szalonym tempie. Domyślne przeciążenie operatora czasomierza spowoduje umieszczenie komunikatów OnNext w puli wątków.

Observable.Timer(Timespan.FromSeconds(0.01))
          .Subscribe(…);

Spowoduje to szybkie kolejki do obserwatora. Możemy ulepszyć ten kod przy użyciu operatora ObserveOn, który umożliwia określenie kontekstu, którego chcesz użyć do wysyłania powiadomień wypychanych (OnNext) do obserwatorów. Domyślnie operator ObserveOn zapewnia, że funkcja OnNext będzie wywoływana tak wiele razy, jak to możliwe w bieżącym wątku. Możesz użyć jego przeciążeń i przekierować dane wyjściowe OnNext do innego kontekstu. Ponadto można użyć operatora Subskrybuj, aby zwrócić serwer proxy, który deleguje akcje do określonego harmonogramu. Na przykład w przypadku aplikacji intensywnie korzystającej z interfejsu użytkownika można delegować wszystkie operacje w tle, które mają być wykonywane na harmonogramie uruchomionym w tle przy użyciu opcji Subskrybuj i przekazując do niej element ThreadPoolScheduler. Aby otrzymywać powiadomienia wypychane i uzyskiwać dostęp do dowolnego elementu interfejsu użytkownika, możesz przekazać wystąpienie dyspozytoraScheduler do operatora ObserveOn.

W poniższym przykładzie zostaną zaplanowane wszystkie powiadomienia OnNext w bieżącym dyspozytocie, aby każda wartość wypchnięta została wysłana w wątku interfejsu użytkownika. Jest to szczególnie korzystne dla deweloperów programu Silverlight korzystających z języka Rx.

Observable.Timer(Timespan.FromSeconds(0.01))
          .ObserveOn(Scheduler.DispatcherScheduler)
          .Subscribe(…);

Zamiast używać operatora Obserwowano, aby zmienić kontekst wykonywania, na którym sekwencja obserwowana generuje komunikaty, możemy utworzyć współbieżność we właściwym miejscu, aby rozpocząć od. Ponieważ operatory parametryzują wprowadzenie współbieżności, zapewniając przeciążenie argumentu harmonogramu, przekazanie odpowiedniego harmonogramu doprowadzi do mniejszej liczby miejsc, w których operator ObserveOn musi być używany. Na przykład możemy odblokować obserwatora i zasubskrybować wątek interfejsu użytkownika bezpośrednio, zmieniając harmonogram używany przez źródło, jak w poniższym przykładzie. W tym kodzie przy użyciu przeciążenia czasomierza, które przyjmuje harmonogram, i podając Scheduler.Dispatcher wystąpienie, wszystkie wartości wypchnięte z tej widocznej sekwencji pochodzą z wątku interfejsu użytkownika.

Observable.Timer(Timespan.FromSeconds(0.01), Scheduler.DispatcherScheduler)
          .Subscribe(…);

Należy również pamiętać, że przy użyciu operatora ObserveOn akcja jest zaplanowana dla każdego komunikatu, który przechodzi przez oryginalną sekwencji, którą można obserwować. Może to spowodować zmianę informacji o chronometrażu, a także dodatkowe obciążenie systemu. Jeśli masz zapytanie, które komponuje różne obserwowane sekwencje uruchomione w wielu różnych kontekstach wykonywania i wykonujesz filtrowanie w zapytaniu, najlepiej umieścić obserwowane w dalszej części zapytania. Jest to spowodowane tym, że zapytanie potencjalnie odfiltruje wiele komunikatów, a umieszczenie operatora Obserwowano wcześniej w zapytaniu wykona dodatkową pracę nad komunikatami, które mimo to zostaną odfiltrowane. Wywołanie operatora ObserveOn na końcu zapytania spowoduje utworzenie najmniejszego wpływu na wydajność.

Kolejną zaletą jawnego określenia typu harmonogramu jest to, że można wprowadzić współbieżność na potrzeby wydajności, jak pokazano w poniższym kodzie.

seq.GroupBy(...)
        .Select(x=>x.ObserveOn(Scheduler.NewThread))
        .Select(x=>expensive(x))  // perform operations that are expensive on resources