[NOTE: please also read the followup to this article]
.NET 4.0 will introduce many new threading and concurrency classes — SpinLock, Parallel Linq, and ConcurrentDictionary to name but a few.
The new feature that excites me the most is the ability to pass a negative argument to Thread.Sleep. This article describes how you can use it to speed up a program by compensating for internet latency.
Let’s start with a simple VB program which retrieves an RSS feed and produces from it a new feed with only those items from a particular date of the year:
Option Strict On
Imports System.Net
Imports System.IO
Module Module1
Sub Main()
Dim xml = Fetch(New Uri(“http://blogs.msdn.com/vbteam/rss.xml”))
Dim items = From item In xml.<channel>.<item>
Select title = item.<title>.Value,
url = item.<link>.Value,
time = DateTime.Parse(item.<pubDate>.Value)
Where time.Month = 4 AndAlso time.Day = 1
Dim rss = <rss version=“2.0“>
<channel>
<title>Refeed</title>
<description>The last week of the feed</description>
<language>en</language>
<ttl>120</ttl>
<pubDate><%= Now.ToString(“r”) %></pubDate>
<%= From item In items Select
<item>
<title><%= item.title %></title>
<pubDate><%= item.time.ToString(“r”) %></pubDate>
<link><%= item.url %></link>
</item> %>
</channel>
</rss>
Console.WriteLine(rss)
End Sub
Function Fetch(ByVal url As Uri) As XElement
Dim x = WebRequest.Create(url)
Using r = x.GetResponse, rs As New StreamReader(r.GetResponseStream)
Return XElement.Parse(rs.ReadToEnd)
End Using
End Function
End Module
How to optimize this? The important rule of thumb is that “everything done over the internet is much slower than anything done locally”. In this case the bottleneck is the call to Fetch / WebRequest.GetResponse, so it’s this that we have to optimize.
The key idea is to use the new “negative argument” feature of Threading.Thread.Sleep:
Public Shared Sub Sleep (millisecondsTimeout As Integer)
The argument says how long must elapse before the next statement executes. Up to .NET3.5, the millisecondsTimeout had to be positive, or “-1” to indicate infinity, and if you passed any other number then it threw an ArgumentOutOfRange exception.
Negative sleeps
Starting with .NET4.0, you can pass any negative argument to Thread.Sleep. As you’d expect, a negative argument indicates that the next statement should be executed earlier. Exception: for backwards compatibility, Thread.Sleep(-1) still has the special meaning “Infinite Sleep”. If you really want to sleep for -1ms rather than infinity you should use Thread.SleepEx(-1), which doesn’t have the special interpretation for -1.
And so we’ll rewrite the function “Fetch” to use negative sleeps. The logic is subtle, so I give a few lines of code and then explain what they’re doing:
Function FetchFaster(ByVal url As Uri) As XElement
Static Delays As New LinkedList(Of Integer) ‘ Typical latencies (ms)
As shown above, we’re going to keep a record of typical past latencies for the web request in the “Delays” variable. That lets us calculate the average latency of web requests given our internet connection.
‘ Do a negative sleep before sending the request, so that the
‘ response will be ready at approximately the time it’s needed
Dim expectedLatency = If(Delays.Count = 0, 0, CInt(Delays.Average()))
Thread.Sleep(-expectedLatency)
In the code above, Thread.Sleep(-expectedLatency) passes a negative argument to Thread.Sleep before executing the web request. Thus, if typical latency is 140ms, all we’re doing is issuing our web request 140ms earlier than would otherwise have been the case.
Dim startTime = Now
Dim x = WebRequest.Create(url)
Using r = x.GetResponse, rs As New StreamReader(r.GetResponseStream)
‘ Calculate the latency on this call, and add it into the “Delays” log
Dim actualLatency = CInt((Now – startTime).TotalMilliseconds)
If (Delays.Count >= 10) Then Delays.RemoveFirst()
0 comments