Building on my last post, I am going to use Tasks
to retrieve and display RSS feeds in a WPF application. Since we are retrieving the feeds over the internet, we really don't need to use threads, as it isn't a CPU bound operation. Instead, we can leverage the existing .NET asynchronous event pattern that classes like WebClient
already implement.
The guys over at Parallel Programming with .NET Blog have a great write-up about how to encapsulate this pattern into a Task
. It seems they've also written extension methods for common BCL objects that implement this pattern in the Parallel Programming Samples.
Instead of constructing a Task
directly, using this pattern, we retrieve a Task
from a TaskCompletionSource
. This object contains methods for controlling a Task
object that it manages. After creating a TaskCompletionSource
, we execute the asynchronous operation, and report to the TaskCompletionSource
when the operation is finished, cancelled, or has an error. By putting all this logic into a static function, we make it really easy to create a Task
to asynchronously retrieve a Stream
from a Uri
:
private static Task<Stream> StartReadTask(Uri address)
{
WebClient webClient = new WebClient();
var tcs = new TaskCompletionSource<Stream>(address,
TaskCreationOptions.DetachedFromParent);
OpenReadCompletedEventHandler handler = null;
handler = (sender, e) =>
{
if (e.UserState == tcs)
{
if (e.Cancelled)
tcs.TrySetCanceled();
else if (e.Error != null)
tcs.TrySetException(e.Error);
else
tcs.TrySetResult(e.Result);
}
webClient.OpenReadCompleted -= handler;
};
webClient.OpenReadCompleted += handler;
try
{
webClient.OpenReadAsync(address, tcs);
}
catch
{
webClient.OpenReadCompleted -= handler;
tcs.TrySetCanceled();
throw;
}
return tcs.Task;
}
As you can see, this function is responsible for actually calling the methods on the WebClient
class that we are using to retrieve a Stream
to the given Uri
. It returns a Task
, but it is slightly different from other Tasks
that we are used to using. For instance, if you try to call Start
on it, an exception will be thrown. This is because the Task
isn't encapsulating work, it is more like a reporting mechanism for work being done. But, we can still use it to determine when the work has completed, like so:
Uri url = new Uri(uri);
Task<Stream> t = StartReadTask(url);
t.ContinueWith(ReadRssStream, TaskContinuationOptions.DetachedFromParent,
TaskScheduler.FromCurrentSynchronizationContext());
Since we are using the TaskScheduler
from our current synchronization context (our UI thread), the ReadRssStream
method will be run on our UI thread. In this method, we take the Stream
that was retrieved in our Task
, and attempts to parse it into RSS or Atom, and adds the items to the ObservableCollection
of SyndicationItems
:
private void ReadRssStream(Task<Stream> task)
{
if (task.Status == TaskStatus.RanToCompletion)
{
XmlReader rdr = XmlReader.Create(task.Result);
IEnumerable<SyndicationItem> items;
if (!TryReadRss(rdr, out items))
TryReadAtom(rdr, out items);
if (items == null)
{
Error = "No Rss feed found at the given url.";
}
else
{
_rssItems.Clear();
foreach (var item in items)
_rssItems.Add(item);
}
}
else if (task.Exception != null)
{
Error = "Address Not Found.";
}
}
In our Window
, we just bind a ListBox
to the list of feeds. The DataTemplate
could probably be improved to show a prettier representation of the content, but this worked for this sample app:
<ListBox x:Name="_feeds"
DockPanel.Dock="Left"
Width="350"
ItemsSource="{Binding RssItems}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
HorizontalContentAlignment="Stretch"
SelectionChanged="OnSelectedFeedChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock FontSize="14"
FontWeight="Bold"
TextTrimming="CharacterEllipsis"
Text="{Binding Title.Text}"
Margin="0,0,0,4"/>
<TextBlock TextWrapping="Wrap"
Text="{Binding Summary.Text}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I'm really starting to like the new Task API. It definitely seems to make writing parallel / asynchronous code easier. Maybe after playing with it a bit more I'll have some complaints, but it certainly has been pretty nice so far!
Since this post is using features new to .NET 4.0, it was built using VS 2010 Beta 1.
Here is the source: RssViaTasks.zip
-AH