bling.github.io

This blog has relocated to bling.github.io.

Thursday, September 8, 2011

Building a Real-time Push App with Silverlight: Part 5

I planned on this post to be about UI, but I’m going to defer that until the next post.  I said from the start of this series that I would document about everything about building the application from scratch, including my struggles.

And with that I want to mention something that got me scratching my head one too many times.  It was with how I used LinqToTwitter.  Here is the source code which you can immediately copy/paste into a blank project to reproduce:

   1: public partial class MainPage : UserControl
   2: {
   3:    private readonly TwitterContext _context = new TwitterContext();
   4:    private readonly ViewModel _vm1, _vm2, _vm3;
   5:  
   6:    public MainPage()
   7:    {
   8:        InitializeComponent();
   9:        _vm1 = new ViewModel(_context);
  10:        _vm2 = new ViewModel(_context);
  11:        _vm3 = new ViewModel(_context);
  12:        _vm1.Callback += () => Debug.WriteLine("Callback of VM1: " + _vm1.LocalState);
  13:        _vm2.Callback += () => Debug.WriteLine("Callback of VM2: " + _vm2.LocalState);
  14:        _vm3.Callback += () => Debug.WriteLine("Callback of VM3: " + _vm3.LocalState);
  15:  
  16:        _vm1.Start();
  17:        _vm2.Start();
  18:        _vm3.Start();
  19:    }
  20: }
  21:  
  22: public class ViewModel
  23: {
  24:    private readonly TwitterContext _context;
  25:    public event Action Callback;
  26:  
  27:    public int LocalState;
  28:  
  29:    public ViewModel(TwitterContext context)
  30:    {
  31:        _context = context;
  32:    }
  33:  
  34:    public void Start()
  35:    {
  36:        var query = (from s in _context.Status
  37:                     where s.Type == StatusType.Public && s.Count == 10
  38:                     select s);
  39:        Debug.WriteLine("Hash code of ViewModel: " + query.GetHashCode());
  40:        query.AsyncCallback(statuses =>
  41:        {
  42:            LocalState++;
  43:            Debug.WriteLine("Hash code inside callback: " + GetHashCode());
  44:            Callback();
  45:        }).FirstOrDefault();
  46:    }
  47: }

Now, if you run this, you will see that only one of the view models will get its state updated.  Huh?!

How is that possible?  I started getting paranoid so I even added the local state variable “just in case.”

Well, I had to look into the source code of LinqToTwitter to figure out exactly what happened.  Here is the code for AsyncCallback:

public static IQueryable<T> AsyncCallback<T>(this IQueryable<T> queryType, Action<IEnumerable<T>> callback)
 {
     (queryType.Provider as TwitterQueryProvider)
         .Context
         .TwitterExecutor
         .AsyncCallback = callback;
 
     return queryType;
 }

See what happened?  The callback gets overwritten every time you call this method.  Even though the call to FirstOrDefault() causes all 3 expressions to evaluate, only the last view model will get values because that’s the with the callback attached.

Lesson of the day: The AsyncCallback extension method for LinqToTwitter is not thread-safe.

So…the question is, how do we make it thread safe?  I just replaced wrapped the AsyncCallback with another extension method:

private static readonly AutoResetEvent _twitterEvt = new AutoResetEvent(true);
 
public static void AsyncTwitterCallback<T>(this IQueryable<T> twitter, Action<IEnumerable<T>> callback)
{
    Observable.Start(() =>
    {
        _twitterEvt.WaitOne();
 
        twitter.AsyncCallback(results =>
        {
            try
            {
                callback(results);
            }
            finally
            {
                _twitterEvt.Set();
            }
        }).FirstOrDefault();
    });
}
Nothing complicated – just a simple wait handle to ensure only 1 thread can go through at a time.

Hopefully upstream fixes this, or at least documents it.

No comments: