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();
});
}
Hopefully upstream fixes this, or at least documents it.
No comments:
Post a Comment