SyndicationLink is not thread-safe or what’s the difference between await and Task.ContinueWith

My WinRT app was generating a ton of random crashes, which were difficult to debug. The crash dump harvested from customers by the Windows 8 system were all pointing to the same Accessviolation exception from unmanaged code.

There is only one place in my C# codebase which would result in the offending code being called,and it was protected by a try/catch block. So the first lesson I learned was that the regular try/catch does not save you from such memory exceptions.

Raising this issue on Microsoft Forums did not produce fruitful results. So… What is there to do?

Well, let’s start with the fact that these errors were almost impossible to reproduce. This points to threading issue.

Looking closer at this code:

var client = new SyndicationClient();
client.BypassCacheOnRetrieve = true;
      
var feed = client.RetrieveFeedAsync(_uri).ContinueWith( t =>
{ 
    var feed = t.Result;
    for each (var si in feed.Items)
     {
           if (si.Links.Count > 0)
            {
                //offending code below
                _link = si.Links[0].Uri; 
            }
     }
}
);

I began to suspect that something was releasing memory resources belonging to the feed and its items before the code retrieving the Uri from one of the syndication items runs.

How to fix this? Replace ContinueWith with the await call like this:

var client = new SyndicationClient();
client.BypassCacheOnRetrieve = true;
var feed = await client.RetrieveFeedAsync(_uri);
for each (var si in feed.Items)
{
    if (si.Links.Count > 0)
    {
       _link = si.Links[0].Uri; 
    }
}

So, what’s the difference? In the first example, the continuation runs on a thread that’s different from the one that initiated loading of the feed. By default, without specifying any additional parameters into the ContinueWith the delegate passed into it will be executed on a ThreadPool thread. So the thread that initiated the task, likely, creates the managed SyndicationFeed reference, and because the thread accessing it is different it’s likely causing this exception.

To use the ContinueWith method safely, you should pass the TaskScheduler.FromCurrentSynchronizationContext reference to the Task Scheduler. This would capture the current thread context and would use it to execute the delegate inside the continuation, making this approach equivalent to using the await.

References:
[1] Stack Overflow: http://stackoverflow.com/questions/8767218/is-async-await-keyword-equivalent-to-a-continuewith-lambda
[2] MSDN: http://msdn.microsoft.com/en-us/library/dd321307.aspx

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s