1

I created an observable collection IObservable<Tweet> with LinqToTwitter as shown below. The problem is that this implementation has a concurrency issue when I dispose of the first observable and subscribe to a new observable.

How can I dispose of the first observable correctly?

(The samples below should be complete and work as they are, just add referenced packages and Twitter credentials.)

Here is an example where this problem occurs:

using System;
using System.Reactive.Linq;

namespace Twitter.Cli
{
    class Program
    {
        public static void Main(string[] args)
        {
            var twitter = new TwitterApi.Twitter();

            var search1 = twitter.AllTweetsAbout("windows")
                .Sample(TimeSpan.FromSeconds(1));

            var search2 = twitter.AllTweetsAbout("android")
                .Sample(TimeSpan.FromSeconds(1));

            var sub = search1.Subscribe(
                x =>
                    Console.WriteLine("TOPIC = {0} - CONTAINS STRING: {1}", x.Topic, x.Text.ToLower().Contains(x.Topic.ToLower()) ? "YES" : "NO"));

            Console.ReadLine();

            sub.Dispose();

            /* 
            * If you stop the processing here for a while so that the StartAsync method can be executed 
            * within the closure everything works fine because disposed is set to true 
            * before the second observable is created 
            */
            //Console.ReadLine(); 

            search2.Subscribe(
                x =>
                    Console.WriteLine("TOPIC = {0} - CONTAINS STRING: {1}", x.Topic, x.Text.ToLower().Contains(x.Topic.ToLower()) ? "YES" : "NO"));

            Console.ReadLine();
        }
    }
}

If the StartAsync method in the closure of the first observable creation is executed before the second observable is created then disposed will be set to true and everything is fine.

But if the second observable is created before the next execution of the first closure in StartAsync disposed is set to false again and s.CloseStream(); is never called.

Here is the creation of the observable:

using System;
using System.Configuration;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using LinqToTwitter;

namespace TwitterApi
{
    public class Twitter
    {
        private readonly SingleUserAuthorizer _auth = new SingleUserAuthorizer
        {
            CredentialStore = new InMemoryCredentialStore
            {
                ConsumerKey = ConfigurationManager.AppSettings["consumerKey"],
                ConsumerSecret = ConfigurationManager.AppSettings["consumerSecret"],
                OAuthToken = ConfigurationManager.AppSettings["authtoken"],
                OAuthTokenSecret = ConfigurationManager.AppSettings["authtokensecret"],
            }
        };

        private readonly TwitterContext _twitterCtx;

        public Twitter()
        {
            if (String.IsNullOrWhiteSpace(_auth.CredentialStore.ConsumerKey)
                || String.IsNullOrWhiteSpace(_auth.CredentialStore.ConsumerSecret)
                || String.IsNullOrWhiteSpace(_auth.CredentialStore.OAuthToken)
                || String.IsNullOrWhiteSpace(_auth.CredentialStore.OAuthTokenSecret))
                throw new Exception("User Credentials are not set. Please update your App.config file.");

            _twitterCtx = new TwitterContext(_auth);
        }

        public IObservable<Tweet> AllTweetsAbout(string topic)
        {
            return Observable.Create<Tweet>(o =>
            {
                var query = from s in _twitterCtx.Streaming
                            where s.Type == StreamingType.Filter &&
                                    s.Track == topic
                            select s;

                var disposed = false;

                query.StartAsync(async s =>
                {
                    if (disposed)
                        s.CloseStream();
                    else
                    {
                        Tweet t;
                        if (Tweet.TryParse(s.Content, topic, out t))
                        {
                            o.OnNext(t);
                        }
                    }
                });

                return Disposable.Create(() => disposed = true);
            });
        }
    }
}

And finally the Tweet class:

using System;
using Newtonsoft.Json.Linq;

namespace TwitterApi
{
    public class Tweet
    {
        public string User { get; private set; }
        public string Text { get; private set; }
        public string Topic { get; private set; }

        public static bool TryParse(string json, string topic, out Tweet tweet)
        {
            try
            {
                dynamic parsed = JObject.Parse(json);
                tweet = new Tweet
                {
                    User = parsed.user.screen_name,
                    Text = parsed.text,
                    Topic = topic,
                };
                return true;
            }
            catch (Exception)
            {
                tweet = null;
                return false;
            }
        }

        private Tweet()
        {

        }
    }
}
leifbattermann
  • 622
  • 7
  • 12

0 Answers0