Imports System.Collections.Concurrent Imports System.Threading.Tasks Imports System.Threading Imports System.Net Public Class AsyncCache(Of TKey, TValue) Public Sub New(ByVal l As Func(Of TKey, TValue)) _loader = l End Sub Public Sub GetValueAsync(ByVal key As TKey, ByVal callback As Action(Of TValue)) Dim task As Task(Of TValue) = Nothing If Not _map.TryGetValue(key, task) Then ' Is the task in the cache? (Loc. X) task = New Task(Of TValue)(Function() _loader(key), TaskCreationOptions.DetachedFromParent) ' No, ok then create it If _map.TryAdd(key, task) Then ' Try to add it task.Start() ' I succeeded, I can safely start the thread Else task.Cancel() ' I failed, that means that someone inserted the task after I checked in Loc. X. I need to cancel it. _map.TryGetValue(key, task) ' And get the one that someone inserted End If End If task.ContinueWith(Sub(t) callback(t.Result)) End Sub Private _loader As Func(Of TKey, TValue) Private _map As New ConcurrentDictionary(Of TKey, Task(Of TValue)) End Class Public Class HtmlCache Public Sub New() 'Does changing this make a difference? 'ServicePointManager.DefaultConnectionLimit = 20 End Sub Public Sub GetHtmlAsync(ByVal url As String, ByVal callback As Action(Of String)) _asyncCache.GetValueAsync(url, callback) End Sub Private Function LoadWebPage(ByVal url As String) As String Using client As New WebClient() 'Test.PrintThread("Downloading on thread {0} ...") Return client.DownloadString(url) End Using End Function Private _asyncCache As New AsyncCache(Of String, String)(AddressOf LoadWebPage) End Class Public Class Test Public Shared Sub Main() Dim tickers = New String() {"mmm", "aos", "shlm", "cas", "abt", "anf", "abm", "akr", "acet", "afl", "agl", "adc", "apd", "ayr", "alsk", "ain", "axb", "are", "ale", "ab", "all"} Dim sitesToDownload = 50 Dim workToDoOnEachUrlInMilliSec = 20 Dim perfIterations = 5 TestPerf("Async", Sub() TestAsync(tickers, sitesToDownload, workToDoOnEachUrlInMilliSec), perfIterations) TestPerf("Sync", Sub() TestSync(tickers, sitesToDownload, workToDoOnEachUrlInMilliSec), perfIterations) End Sub Private Shared Sub TestAsync(ByVal sites() As String, ByVal sitesToDownload As Integer, ByVal howLong As Integer) Dim htmlCache As New HtmlCache Dim count = sites.Count() Dim url = "http://moneycentral.msn.com/investor/invsub/results/statemnt.aspx?Symbol=" Using ce = New CountdownEvent(sitesToDownload) For i = 1 To sitesToDownload htmlCache.GetHtmlAsync( url & sites(i Mod count), Sub(s) DoWork(s, howLong) ce.Signal() End Sub) Next ce.Wait() End Using End Sub Private Shared Sub TestSync(ByVal sites() As String, ByVal sitesToDownload As Integer, ByVal howLong As Integer) Dim syncCache As New Dictionary(Of String, String) Dim count = sites.Count() Dim url1 = "http://moneycentral.msn.com/investor/invsub/results/statemnt.aspx?Symbol=" For i = 0 To sitesToDownload - 1 Dim html As String = "" Dim url = url1 & sites(i Mod count) If Not syncCache.TryGetValue(url, html) Then html = LoadWebPage(url) syncCache(url) = html End If DoWork(html, howLong) Next End Sub Public Shared Sub DoWork(ByVal html As String, ByVal howLong As Integer) Thread.Sleep(howLong) End Sub Public Shared Sub PrintThread(ByVal s As String) Console.WriteLine(s, Thread.CurrentThread.ManagedThreadId) End Sub Private Shared Sub TestPerf(ByVal s As String, ByVal a As Action, ByVal iterations As Integer) Dim clock As New Stopwatch a() clock.Start() For i = 1 To iterations a() Next clock.Stop() Dim ts = clock.Elapsed Dim elapsedTime = String.Format(s & ": {0:00}:{1:00}:{2:00}.{3:00}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds / 10) Console.WriteLine(elapsedTime, "RunTime") End Sub Private Shared Function LoadWebPage(ByVal url As String) As String Using client As New WebClient() 'Test.PrintThread("Downloading on thread {0} ...") Return client.DownloadString(url) End Using End Function End Class