2010-09-08 16 views
6

Cela pourrait être une question stupide que je suis un peu nouveau pour RX :)Modifier l'intervalle des opérateurs RX?

J'échantillonnant un événement (RX pour .NET 4.0):

eventAsObservable.Sample (TimeSpan.FromSeconds (1)). Timestamp(). Subscribe (x => Console.WriteLine ("testing:" + x.Value.EventArgs.str)); Le problème est que le temps d'échantillonnage doit être capable de changer à la volée, je suppose que je pourrais faire une propriété qui supprime le gestionnaire existant et en crée un nouveau quand il change, mais il semble un peu brouillon et plus vulnérable aux problèmes de synchronisation. Y a-t-il un moyen de simplement changer l'intervalle?

Exemple: Dire que quelqu'un est en train d'écrire une chaîne de caractères, lorsqu'une certaine séquence est détecté que vous voulez changer le temps d'échantillonnage sans manquer un événement, et de préférence par ne pas obtenir un événement plus d'une fois

+0

Quel est votre scénario? –

+0

C'est une auto-complétion mais l'intervalle d'échantillonnage est différent selon la source de données (puisque les recherches locales sont plus rapides que les webservices par exemple) – Homde

Répondre

7

I Je ne sais pas d'une façon de changer l'intervalle d'échantillonnage existant, mais ce que pourrait faire est d'échantillonner à la fréquence la plus élevée dont vous aurez besoin, puis filtrer avec une clause Where qui utilise une variable peut changer.

Par exemple:

static IObservable<T> SampleEvery<T>(this IObservable<T> source, 
    Func<int> multipleProvider) 
{ 
    int counter = 0; 
    Func<T, bool> predicate = ignored => { 
     counter++; 
     if (counter >= multipleProvider()) 
     { 
      counter = 0; 
     } 
     return counter == 0; 
    }; 
    return source.Where(predicate); 
} 

Vous souhaitez alors appeler comme ça:

// Keep this somewhere you can change it 
int multiple = 1; 

eventAsObservable.Sample(TimeSpan.FromSeconds(1)) 
       .SampleEvery(() => multiple) 
       .Timestamp() 
       .Subscribe(x => Console.WriteLine("testing:" + 
                x.Value.EventArgs.str)); 

Maintenant, en changeant la valeur de multiple va changer la fréquence d'échantillonnage efficace.

Il est un hack assez laid, mais je pense que cela devrait fonctionner.

+0

Avez-vous manqué un() sur "if (counter> = multipleProvider)" –

+0

@Paul: Oui, oups. Fixation. –

+0

On dirait une solution viable, je vais lui donner quelques tests, merci! Je me demande quelle serait une bonne solution pour des scénarios comme ceux-ci ... peut-être en mesure d'envoyer une méthode/lambda qui renvoie une durée au lieu de la durée réelle. Il ne semble pas si farfelu que vous voulez changer les paramètres à différents opérateurs à la volée – Homde

0

Pourquoi ne pas vous abonner deux fois? Ou si les recherches ne sont actives que sur la base d'un préfixe ou d'un qualificatif tel que "?" De Google Chrome, par exemple. opérateur:

Observable.Merge(
    eventAsObservable.Sample(TimeSpan.FromSeconds(1)).Where(x => isLocal(x)).SelectMany(x => doLocalLookup(x)), 
    eventAsObservable.Sample(TimeSpan.FromSeconds(10)).Where(x => isARemoteQuery(x).SelectMany(x => doRemoteLookup(x)), 
).Subscribe(Console.WriteLine); 
+0

L'intervalle peut être n'importe quelle valeur, il est personnalisable sur une base par source . La meilleure solution consiste à créer un nouvel abonnement et ensuite à disposer de l'ancien, il y a une petite chance que l'événement soit déclenché deux fois. Ce serait génial s'il y avait un moyen d'accéder à la propriété réelle bien que – Homde

+0

Ce que je dis cependant, c'est que vous pouvez simplement les garder tous en cours d'exécution, mais basculer leur sortie via une clause Where. Peut-être que je ne comprends pas exactement ce que vous faites ... –

5

Je sais que cette question a déjà été répondue, mais j'ai pensé que j'ajouterais quelques autres façons de l'aborder de manière Rx.

Vous pouvez utiliser Switch sur une séquence de TimeSpan « s:

private Subject<TimeSpan> sampleFrequencies = new Subject<TimeSpan>(); 

sampleFrequencies 
    .Select(x => eventAsObservable.Sample(Observable.Interval(x)).Timestamp()) 
    .Switch() 
    .Subscribe(x => .WriteLine("testing:" + x.Value.EventArgs.str)); 

// To change: 
// sampleFrequencies.OnNext(TimeSpan.FromSeconds(5)); 

Alternativement, il pourrait également être résolu en utilisant Defer, TakeUntil et Repeat (celui-ci est un peu plus fou et est inclus comme un exercice de pensée):

private TimeSpan sampleFrequency = TiemSpan.FromSeconds(2); 
private Subject<Unit> frequencyChanged = new Subject<Unit>(); 

(Observable 
    .Defer(() => eventAsObservable 
     .Sample(Observable.Interval(sampleFrequency) 
    ) 
    .Timestamp() 
    .TakeUntil(frequencyChanged) 
).Repeat() 
.Subscribe(x => .WriteLine("testing:" + x.Value.EventArgs.str)); 

// To change: 
// sampleFrequency = TimeSpan.FromSeconds(5); 
// frequencyChanged.OnNext(new Unit()); 
+0

En fait, j'ai fini par faire une propriété qui a créé un nouvel abonnement et ensuite éliminé l'ancien. Il y a une petite chance que ça déclenche deux fois un événement, mais je pense que le risque d'introduire des bugs/overhead avec les autres méthodes en fait la solution la plus intéressante. Je pourrais par exemple vouloir changer Sample to Throttle. Les autres solutions semblaient un peu trop bidouillées, mais j'apprécie l'aide! – Homde

+0

Les deux ma solution utilisent toujours Sample, donc il pourrait facilement être changé en Throttle (l'horodatage a été pris directement à partir de vos besoins). La version Switch fait à peu près ce que vous faites maintenant (annulation, redémarrage), mais à partir de Switch. –

+0

Belle solution avec Switch, merci. –

2

TL; DR: Créer une observable en utilisant ObservableFromIntervalFunctor, comme cela est indiqué ci-dessous:

void Main() 
{ 
    // Pick an initial period, it can be changed later. 
    var intervalPeriod = TimeSpan.FromSeconds(1); 

    // Create an observable using a functor that captures the interval period. 
    var o = ObservableFromIntervalFunctor(() => intervalPeriod); 

    // Log every value so we can visualize the observable. 
    o.Subscribe(Console.WriteLine); 

    // Sleep for a while so you can observe the observable. 
    Thread.Sleep(TimeSpan.FromSeconds(5.0)); 

    // Changing the interval period will takes effect on next tick. 
    intervalPeriod = TimeSpan.FromSeconds(0.3); 

} 

IObservable<long> ObservableFromIntervalFunctor(Func<TimeSpan> intervalPeriodFunctor) 
{ 
    return Observable.Generate(0L, s => true, s => s + 1, s => s, s => intervalPeriodFunctor()); 
} 

Explication: Observable.Generate a une surcharge qui vous permet de spécifier le moment où la valeur suivante sera générée par un foncteur. En passant un foncteur qui a capturé une variable de timespan, vous pouvez rendre l'observable.changement de période d'intervalle en changeant la variable de laps de temps capturée.

LINQPad snippet here

+0

Remarque: Si l'intervalle passe de plus lent à plus rapide, cette approche doit attendre que l'intervalle lent en cours se termine avant que la modification de l'intervalle ne prenne effet. Ceci est en contraste avec la réponse de Jon Skeet, qui prendra effet après le temps d'échantillonnage minimum. – r590