Waiting for all Tasks in Async to complete - vb.net

I am trying to run 10 processes simultaneously and when they finish I want to display a messagebox. The UI should remain responsive.
I have this function (MultiProcessImages) which displays a message before all the images finish:
'Start multi processing of images
Public Shared Async Function MultiProcessImages() As Task
'Create a task factory and add 10 process one for each ending image number
Dim t As Task = Nothing
For i As Integer = 0 To 9
Dim temp_i As Integer = i
t = Task.Factory.StartNew(Function() Cheque.CopyBinaryValueToFile(temp_i))
Await t
Next
End Function
Then I created another function which makes the UI unresponsive and also the processing does not seem to be multitasking (which means it does not perform all the actions of the CopyBinaryValueToFile in parallel but only for each task number):
Public Shared Async Function MultiProcessImages**2**() As Task
Dim tasks As New List(Of Task)()
For i As Integer = 0 To 9
Dim temp_i As Integer = i
tasks.Add(Cheque.CopyBinaryValueToFile(temp_i))
' tasks.Add(Task.Factory.StartNew(Function() Cheque.CopyBinaryValueToFile(temp_i)))
Next
Await Task.WhenAll(tasks)
MessageBox.Show("done")
End Function
Any ideas how to make it behave like I have it in the first function but wait until all the processes finish to display a message?
EDIT
I'm calling the function like this:
Private Async Sub cmdConvert_Click(sender As Object, e As EventArgs) Handles cmdConvert.Click
'Await Cheque.CopyBinaryValueToFile()
'{Time count
Dim start_time As DateTime
Dim stop_time As DateTime
Dim elapsed_time As TimeSpan
start_time = Now
'}
Await Cheque.MultiProcessImages2()
'{Time count
stop_time = Now
elapsed_time = stop_time.Subtract(start_time)
MessageBox.Show("elapsed_time = " & stop_time.Subtract(start_time).ToString & Environment.NewLine _
& elapsed_time.TotalSeconds.ToString("0.000000"))
'}
End Sub

Without seeing the rest of your code and the implementation of Cheque.CopyBinaryValueToFile(), the following may help you resolve the issue of UI being blocked:
Within MultiProcessImages2():
tasks.Add(Task.Run(Function() Cheque.CopyBinaryValueToFile(temp_i)))

Related

Ping multiple device names (hostname) on the Network

A DataGridView displays hostnames at Column index 0, computer / printer names on the network.
pc1
pc2
print3
pc5
print
....
There are more than 500 such names.
I know how to ping them:
For i = 0 To DataGridView1.Rows.Count - 1
Try
If My.Computer.Network.Ping(DataGridView1.Item(0, i).Value) = True Then
DataGridView1.Rows(i).DefaultCellStyle.BackColor = Color.Lime
Else
DataGridView1.Rows(i).DefaultCellStyle.BackColor = Color.Red
End If
Catch ex As Exception
DataGridView1.Rows(i).DefaultCellStyle.BackColor = Color.Red
End Try
Next
The problem is that the Ping takes a very long time and the application freezes.
How can you speed up this procedure?
And let's say if the node is not available, then simply remove it from the list.
An example to Ping multiple addresses at the same time, using the async version of provided by the Ping class, Ping.SendPingAsync().
This version is await-able, not the same as the Ping.SendAsync() method, still asynchronous but event-driven.
Since you're using a DataGridView to both store the IpAddress/HostName and to present the PingReply results, you need to determine a way to match the Ping result to correct Cell of the DataGridView from which the Ip/Host address was taken.
Here, I'm passing to the method the Row's Index, so when the Ping result comes back, asynchronously, we can match the response to a specific Cell in the DataGridView.
To make the initialization method more generic, I'm passing also the index of the Column where the Ip/Host address is stored and the index of the Column that will show the result (you could also just pass all indexes, not a DataGridView Control reference to the method and handle the results in a different way).
A loop extracts the addresses from the the DataGridView and creates a List(Of Task), adding a PingAsync() Task for each address found.
When the collection is completed, the List(Of Task) is passed to the Task.WhenAll() method, which is then awaited.
This method starts all the Task in the list and returns when all Task have a result.
► Note that the Ping procedure sets a TimeOut, to 5000ms here, so all the Tasks will return before or within that interval, successful or not.
You can then decide if you want to reschedule the failed Pings or not.
The UI update is handled using a Progress delegate. It's just a method (Action delegate) that is called when the Ping procedure has a result to show.
It can also be used when the method that updates the UI runs in a different Thread: the Report() method will call the Progress object delegate in the Thread that created the delegate: the UI Thread, here (in the example, we're not actually ever leaving it, though).
This is how it works:
Assume you're starting the ping sequence from Button.Click event handler.
Note that the handler is declared async.
Private Async Sub btnMassPing_Click(sender As Object, e As EventArgs) Handles btnMassPing.Click
Await MassPing(DataGridView1, 1, 2)
End Sub
Initialization method and IProgress<T> report handler:
Imports System.Drawing
Imports System.Net.NetworkInformation
Imports System.Net.Sockets
Imports System.Threading.Tasks
Private Async Function MassPing(dgv As DataGridView, statusColumn As Integer, addressColumn As Integer) As Task
Dim obj = New Object()
Dim tasks = New List(Of Task)()
Dim progress = New Progress(Of (sequence As Integer, reply As Object))(
Sub(report)
SyncLock obj
Dim status = IPStatus.Unknown
If TypeOf report.reply Is PingReply Then
status = DirectCast(report.reply, PingReply).Status
ElseIf TypeOf report.reply Is SocketError Then
Dim socErr = DirectCast(report.reply, SocketError)
status = If(socErr = SocketError.HostNotFound,
IPStatus.DestinationHostUnreachable,
IPStatus.Unknown)
End If
Dim color As Color = If(status = IPStatus.Success, Color.Green, Color.Red)
Dim cell = dgv(statusColumn, report.sequence)
cell.Style.BackColor = color
cell.Value = If(status = IPStatus.Success, "Online", status.ToString())
End SyncLock
End Sub)
For row As Integer = 0 To dgv.Rows.Count - 1
If row = dgv.NewRowIndex Then Continue For
Dim ipAddr = dgv(addressColumn, row).Value.ToString()
tasks.Add(PingAsync(ipAddr, 5000, row, progress))
Next
Try
Await Task.WhenAll(tasks)
Catch ex As Exception
' Log / report the exception
Console.WriteLine(ex.Message)
End Try
End Function
PingAsync worker method:
Private Async Function PingAsync(ipAddress As String, timeOut As Integer, sequence As Integer, progress As IProgress(Of (seq As Integer, reply As Object))) As Task
Dim buffer As Byte() = New Byte(32) {}
Dim ping = New Ping()
Try
Dim options = New PingOptions(64, True)
Dim reply = Await ping.SendPingAsync(ipAddress, timeOut, buffer, options)
progress.Report((sequence, reply))
Catch pex As PingException
If TypeOf pex.InnerException Is SocketException Then
Dim socEx = DirectCast(pex.InnerException, SocketException)
progress.Report((sequence, socEx.SocketErrorCode))
End If
Finally
ping.Dispose()
End Try
End Function

Task.WaitAll() in DataGridView.RowEnter event causes DataError

I am using Advanced DataGridView (https://github.com/davidegironi/advanceddatagridview) to display some data in my application.
Within the RowEnter event I create tasks like this:
Dim One As String = dgv.Item("Col1", e.RowIndex)
Dim Two As String = dgv.Item("Col2", e.RowIndex)
Dim Three As String = dgv.Item("Col3", e.RowIndex)
Dim tOne As Task(Of DataTable) = Task.Run(Function() Return GetData(One) End Function)
Dim tTwo As Task(Of DataTable) = Task.Run(Function() Return GetData(Two) End Function)
Dim tThree As Task(Of DataTable) = Task.Run(Function() Return GetData(Three) End Function)
Task.WaitAll(tOne, tTwo, tThree)
'Do stuff with the results of tasks to update other labels etc on the form
lblDate.Text = tOne.Result.Rows(0).Item("Date").ToString
The reason for this is that if the GetData() function takes, say, 5 seconds to return doing this without tasks would result in a 15 second wait (5+5+5) whereas with tasks it only takes 5 seconds (or whatever the slowest task is) and Task.WaitAll gives control back to UI (unsure on correct terminology) so it feels a bit snappier.
However when I apply a filter I get the following exception repeatedly, where [n] is any number, I've seen indexes of -1 upwards (seems to depend on the number of visible rows?):
System.IndexOutOfRangeException: Index [n] does not have a value.
at System.Windows.Forms.CurrencyManager.get_Item(Int32 index)
at System.Windows.Forms.DataGridView.DataGridViewDataConnection.GetError(Int32 boundColumnIndex, Int32 columnIndex, Int32 rowIndex)
If I remove the Task.WaitAll() or do the functions synchronously I don't get the above error.
I'm completely at a loss as to what the underlying cause of that error is, or what I should do different to avoid it.
I have seen this answer but I'm not sure how to apply to my situation.
Use async event handler and await the tasks using Task.WhenAll
' Mark the event handler with Async so you can use Await in it.
Private Async Sub Grid_RowEnter(sender As Object, e As WhateverEventArgs)
Dim One As String = dgv.Item("Col1", e.RowIndex)
Dim Two As String = dgv.Item("Col2", e.RowIndex)
Dim Three As String = dgv.Item("Col3", e.RowIndex)
Dim tOne As Task(Of DataTable) = Task.Run(Function() Return GetData(One) End Function)
Dim tTwo As Task(Of DataTable) = Task.Run(Function() Return GetData(Two) End Function)
Dim tThree As Task(Of DataTable) = Task.Run(Function() Return GetData(Three) End Function)
' await all the tasks
Await Task.WhenAll(tOne, tTwo, tThree)
' back on UI thread
'Do stuff with the results of tasks to update other labels etc on the form
lblDate.Text = tOne.Result.Rows(0).Item("Date").ToString
End Sub
Reference Async/Await - Best Practices in Asynchronous Programming

I need to use Async/Await in my already running program to make it faster

I am having a program in VB, which searches for image in a website as follows:
www.website.com/1.jpg | www.website.com/2.jpg | www.website.com/3.jpg
in loop, and the program opens up only 3.jpg in browser, since 1 and 2 jpg does not exit in server. The program is up and running, but is very very slow, around 120 searches in a minute. However one of my colleagues designed the same program in Angular and that program is running very fast, around 500/600 searches in a minute.
He told me, what he did is generated 50 asynchronous calls to server, and then again 50, and then again 50 and went on like this. thus making his program very fast and ovbiously accurate.
I studied and learnt, that in Visual Basic too, we have async and wait calls to server request, but I cannot figure out how.
Here goes my existing code. Can anyone help me.
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
Dim MYURL as string
itemsF = 0
While Not itemsF = 50000
MYURL = "www.website.com/" & itemsF & ".jpg"
CheckPageExists(MYURL)
itemsF=itemsF+1
End While
End Sub
Private Function CheckPageExists(ByVal url As String) As Boolean
Dim request As Net.HttpWebRequest
Dim response As Net.HttpWebResponse
request = Net.WebRequest.Create(url)
request.Timeout = 5000
Try
response = request.GetResponse()
Catch ex As Exception
'IMAGE DOES NOT EXITS
Exit Function
End Try
Process.Start(url)
End Function
First, you have to make CheckPageExists an Async method:
Private Async Function CheckPageExists(ByVal url As String) As Task(Of Boolean)
Dim request As Net.HttpWebRequest = Net.WebRequest.Create(url)
request.Timeout = 5
Dim Result As Boolean
Try
Using response As HttpWebResponse = Await request.GetResponseAsync.ConfigureAwait(False)
Using responseReader As New IO.StreamReader(response.GetResponseStream)
Dim actualResponse As String = Await responseReader.ReadToEndAsync
Result = True
End Using
End Using
Catch ex As Exception
'IMAGE DOES NOT EXITS
Result = False
End Try
Console.WriteLine(url)
''Process.Start("chrome.exe", url)
Return Result
End Function
As you can see, instead of GetResponse we are using GetResponseAsync, which is Async itself. This method is very similar to what you were doing before, I
just added Return statements for clarity and a StreamReader to read the response of your website.
Once you've done that, you just need to change your Button2_Click method to call this other method, which incorporates all you were doing before:
Async Function MakeRequests() As Task
Dim tasks As List(Of Task(Of Boolean)) = New List(Of Task(Of Boolean))
Dim itemsF As Integer = 5
For i = 1 To itemsF
Dim MYURL As String = "http://www.touchegolfschool.com/images/" & i & ".jpg"
tasks.Add(CheckPageExists(MYURL))
Next
While tasks.Select(Function(x) x.Result).Count < tasks.Count
Thread.Sleep(100)
End While
End Function
The main change was adding tasks.Select(Function(x) x.Result).Count < tasks.Count; what you were seeing before was a request made but never returning because of the time it took to return; telling the main function to wait until all requests have a result makes the application wait long enough for the responses to come.
Here the main difference is the use of Tasks.
Check this for official documentation on asynchronous programming.

Need help rewriting a foreach and do until loop

So i am trying to recode this because its not working correctly :
Private Sub StartTrafficExchange()
Dim downloadstring As New StreamReader(Response2.GetResponseStream())
Dim filterstring As String() = downloadstring.ReadToEnd.Split("|")
For Each stirngman As String In filterstring
linklist.Items.Add(stirngman)
Next
Do Until linklist.Items.Count = 0
Dim rand As New Random
linklist.SelectedIndex = rand.Next(0, linklist.Items.Count - 1)
Dim strimgna As String = linklist.SelectedItem
Dim newlinkstring As String() = strimgna.Split("``")
For Each stringma As String In newlinkstring
If stringma.Length < 8 Then
GoTo a
Else
LabelX1.Text = "Navigating To " + stringma
stringma = stringma.Replace("[TIER4]", "")
Debug.WriteLine(stringma)
WebKitBrowser1.Navigate(strimgna)
Thread.Sleep(15000)
End If
LabelX1.Text = "Navigating To " + stringma
ProgressBarX1.Value += 1 a:
Next
linklist.Items.Remove(linklist.SelectedItem)
Loop
ProgressBarX1.Maximum = linklist.Items.Count
ProgressBarX1.Value = 0
StartTrafficExchange()
End Sub
So what does the code do ? Well it fetches a webrequest to a link where the source will be like
http://www.asdfd.com``[TIER4]|http://aesde.com``[TIER4]|http://www.excaedf.com``[TIER4]|
As you can see the above code first splits "|" and adds them each to a list.
Then we split other things to make it as a perfect url and then navigate to it by webkitbrowser... and waits 15 sec by using thread.sleep (Idk if it works.) then removes it from linklist in a foreach and do until loop. So whats the problem ? Well it doesn't work correctly the webkitbrowser is just hanging .
Note : The sub StartTrafficExchange() is called when the form loads.
Can someone tell me whats wrong with the above code and is there anyother way to make this work ?
-Thanks-
Assuming that linklist is not an actual LinkedList but is really a List(Of T) or the list of a List control, you can replace this chunk of code:
Dim filterstring As String() = downloadstring.ReadToEnd.Split("|")
For Each stirngman As String In filterstring
linklist.Items.Add(stirngman)
Next
with this:
Dim filter As String() = downloadstring.ReadToEnd.Split("|")
linklist.AddRange(filter)
which does the same thing, only faster, and with less memory being consumed.
Next, move this way up (like immediately after the AddRange):
ProgressBarX1.Maximum = linklist.Items.Count
I was going to type out why to make the various changes, but here is the whole routine as I would refactor it:
Private Sub StartTrafficExchange()
' Performing one split here that removes everything that needs
' to go will be faster, take less memory, won't have to be
' touched as many times
Dim filter As String() = downloadstring.ReadToEnd.Split("``[TIER4]|")
linklist.AddRange(filter)
' Set your maximum value so it knows when it is full
ProgressBarX1.Maximum = linklist.Items.Count
' Create a counter to let us know how many items
' have been processed
Dim counter As Int = 0
Dim currentURL As String = String.Empty
Do While linklist.Items.Count > 0
' Get the current URL from our list
currentURL = linklist.Items(counter)
LabelX1.Text = String.Format("Navigating To [TIER4] {0}", currentURL)
LabelX1.Refresh() ' Allow the label to update
' This is only good if you have a debugger turned on
' or are running from the IDE
Debug.WriteLine(currentURL)
WebkitBrowser1.Navigate(currentURL)
' Not a good idea as it will block the UI
' from responding. If you need a delay that
' doesn't appear to lock up the UI, implement
' a timer in a loop
Thread.Sleep(15000)
' Increment the counter, then update the progressbar
counter += 1
ProgressBarX1.Value = counter
ProgressBarX1.Refresh() ' Allow the progressbar to update
Loop
' When you are through getting all of the URLs,
' clear the list just one time
linklist.Items.Clear()
' Make recursive call to this sub
' You should limit the number of recursions
' somehow so you don't run out of stack space
StartTrafficExchange
End Sub
If you want to pause the process with blocking the UI, use this:
' Create a new timer object that will run for 1/10 of second
Dim timr As New Timer(100)
' Run this for 150 times at .1 seconds will
' give you a 15 second pause and still leave
' the UI responsive
For iLoop As Integer = 0 To 150
' Start the timer for .1 seconds
timr.Start()
' This tells everything on the form to process updates
DoEvents()
Next
First of all, this is not very important but the code is inconsistent, you are using C# practices but you are in VB.NET, then remove all those + operator on strings appends and replace them with an &.
Second, if your purpose is to wait for the webpage to be fully loaded then using Sleep method is not the way, you need to suscribe to like an WebBrowserDocumentCompleted event of the browser control (I don't know the exact event name for a WebKitBrowser)
You can simplify things by writting a method called NavigateAndWait and use it instead of using WebKitBrowser.Navigate method.
I give you an example for a default WebBrowser control:
Private WebPageLoaded As Boolean = False
''' <summary>
''' Navigates to an url and waits the page to be loaded.
''' </summary>
''' <param name="url">Indicates the url to navigate.</param>
''' <param name="newWindow">Indicates whether the url should open into a new browser window.</param>
Private Sub NavigateAndWait(ByVal Browser As WebBrowser,
ByVal url As String,
Optional newWindow As Boolean = False)
Me.WebPageLoaded = False
AddHandler Browser.DocumentCompleted, AddressOf WebBrowserDocumentCompleted
Browser.Navigate(url, newWindow)
Do Until Me.WebPageLoaded
Application.DoEvents()
Loop
RemoveHandler Browser.DocumentCompleted, AddressOf WebBrowserDocumentCompleted
End Sub
' WebBrowser [DocumentCompleted]
Private Sub WebBrowserDocumentCompleted(ByVal sender As Object, e As WebBrowserDocumentCompletedEventArgs)
Me.WebPageLoaded = True
End Sub
And other for a GeckoFX webbrowser:
Private WebPageLoaded As Boolean = False
''' <summary>
''' Navigates to an url and waits the page to be loaded.
''' </summary>
''' <param name="url">Indicates the url to navigate.</param>
Private Sub NavigateAndWait(Byval Browser as Gecko.GeckoWebBrowser,
Byval url As String,
Optional loadFlags As Gecko.GeckoLoadFlags = Gecko.GeckoLoadFlags.None,
Optional referrer As String = Nothing,
Optional postData As Gecko.GeckoMIMEInputStream = Nothing)
Me.WebPageLoaded = False
AddHandler Browser.DocumentCompleted, AddressOf GeckoWebBrowserDocumentCompleted
Browser.Navigate(url, loadFlags, referrer, postData)
Do Until Me.WebPageLoaded
Application.DoEvents()
Loop
RemoveHandler Browser.DocumentCompleted, AddressOf GeckoWebBrowserDocumentCompleted
End Sub
' GeckoWebBrowser [DocumentCompleted]
Private Sub GeckoWebBrowserDocumentCompleted(ByVal sender As Object, e As EventArgs)
Me.WebPageLoaded = True
End Sub

How to throttle concurrent Async webrequests

I often need to make a large number of webrequests, without overloading the network
I currently do this by running synchronous requests in parallel, utilizing ThreadPool.SetMinThreads and MaxDegreeOfParallelism to exactly specify how many requests run concurrently
Now this works just fine, but it feels wrong.
I would really like to utilize async methods, but i cant work out how to limit the number of concurrent requests.
A simplified example of my parallel way of doing this( using a webclient and no error handling for brevity):
Private Function SearchSitesForKeywordInParallel(ByVal keyword As String, ByVal sites As String(), ByVal maxConcurrency As Integer) As String()
Dim po As New ParallelOptions
po.MaxDegreeOfParallelism = maxConcurrency
Threading.ThreadPool.SetMinThreads(maxConcurrency, 2)
Dim sitesContainingKeyword As New Concurrent.ConcurrentBag(Of String)
Parallel.For(0, sites.Count, po, Sub(i)
Dim wc As New Net.WebClient
wc.Proxy = Nothing
Dim pageSource As String = wc.DownloadString(sites(i))
If pageSource.Contains(keyword) Then
sitesContainingKeyword.Add(sites(i))
End If
End Sub)
Return sitesContainingKeyword.ToArray
End Function
This is a blocking function, which is what i require.
Now i have tested the webclient.downloadStringAsync method in a regular for loop, and it will fire all the requests pretty much at once, overloading the network.
What i would like to do is initially make X requests, then make new ones as each response comes back.
I am fairly sure tasks is the way to go, and im positive a have read some very nice implementations in c#, but my c# experience is limited, and i have a hard time translating c# lambadas to vb.net.
I am also limited to vs2010 and .net4, so the niceties of .net4.5 async await are not an option for me.
Any help very much appreciated
Not sure, if I understand completey, what exactly you want to achieve, but if you want to use aync methods, you can do it like this:
Dim google As String = "http://www.google.com/#&q="
Dim qsites As New Concurrent.ConcurrentQueue(Of String)
For Each k In {"foo", "bar", "john", "jack", "stackoverflow", "basic", "ship", "car", "42"}
qsites.Enqueue(google & k)
Next
Dim cde As New System.Threading.CountdownEvent(qsites.Count)
Dim strings As New Concurrent.ConcurrentBag(Of String)
Dim completedhandler = Sub(wco As Object, ev As Net.DownloadStringCompletedEventArgs)
Dim wc = DirectCast(wco, Net.WebClient)
Debug.Print("got one!")
strings.Add(ev.Result)
cde.Signal()
Dim s As String = String.Empty
If qsites.TryDequeue(s) Then
Debug.Print("downloading from {0}", s)
wc.DownloadStringAsync(New Uri(s))
End If
End Sub
Dim numthreads As Integer = 4
System.Threading.Tasks.Task.Factory.StartNew(Sub()
For i = 1 To numthreads
Dim s As String = String.Empty
If qsites.TryDequeue(s) Then
Dim wc As New Net.WebClient
wc.Proxy = Nothing
AddHandler wc.DownloadStringCompleted, completedhandler
Debug.Print("downloading from {0}", s)
wc.DownloadStringAsync(New Uri(s))
End If
Next
End Sub)
cde.Wait()
You only need to "start" the async downloads in a different thread/task because (afaik) the WC's downloadcompleted events fire in the UI thread (or currentsync..context) and the cde.wait would then not allow the events to be handled.
I just want to add another answer to this as I have recently solved a similar problem (note the code snippet is in C#, but should give the idea).
I used to have number of parallel http synchronous requests sent to http server on different thread and used to limit the number of requests I sent using semaphore.
Now, I have adapted to new TPL (c# 5.0 - aysn/await - quite handy (basically continuation introduced in TPL sound natural to me - and with async/await it has become much easier to use)), to invoke network I/O asynchronously.
i.e. ideally now I will be using only one thread in caller (unless I really need to get results before continuing), and let .net, os and I/o completion port threads work together to invoke my continuation code in thread pool to complete operation (basically 'callback' in APM, on completed event in event based pattern, 'continuation' in TPL, code after await in C# 5.0 (4.5 .net))
The principle I followed when I have embraced async i/o is simple - don't let thread wait and waste CPU and resources, unless it is really necessary!
You can do this asynchronously in VB.NET using the Wintellect Powerthreading library's AsyncEnumerator class, which you can get from NuGet.
This gives you some of the functionality of Await but works in VS2010 with .Net 2.0 to 4.0 while giving you an upgrade path to the 4.5 async features.
The downside is that the WebClient async methods require an EAP-to-APM shim based on Task<> to be used with AsyncEnumerator, so the code is quite a lot more complicated.
The simplest way to control the number of concurrent requests is to initiate X async operations, then just initiate another every time one completes.
Example code:
Imports System.Collections.Generic
Imports System.Runtime.CompilerServices
Imports System.Threading.Tasks
Imports System.Net
Imports Wintellect.Threading.AsyncProgModel
Module TaskExtension
REM http://msdn.microsoft.com/en-us/library/hh873178.aspx
<Extension()>
Public Function AsApm(Of T1)(ByVal task As Task(Of T1), callback As AsyncCallback, state As Object) As IAsyncResult
If (task Is Nothing) Then
Throw New ArgumentNullException("task")
End If
Dim tcs = New TaskCompletionSource(Of T1)(state)
task.ContinueWith(Sub(t As Task(Of T1))
If (t.IsFaulted) Then
tcs.TrySetException(t.Exception.InnerExceptions)
ElseIf t.IsCanceled Then
tcs.TrySetCanceled()
Else : tcs.TrySetResult(t.Result)
End If
If (Not callback Is Nothing) Then
callback(tcs.Task)
End If
End Sub, TaskScheduler.Default)
Return tcs.Task
End Function
End Module
Module ApmAsyncDownload
Public Function DownloadStringAsync(url As Uri) As Task(Of String)
Dim tcs As New TaskCompletionSource(Of String)
Dim wc As New WebClient()
AddHandler wc.DownloadStringCompleted, Sub(s As Object, e As System.Net.DownloadStringCompletedEventArgs)
If (Not (e.Error Is Nothing)) Then
tcs.TrySetException(e.Error)
ElseIf e.Cancelled Then
tcs.TrySetCanceled()
Else : tcs.TrySetResult(e.Result)
End If
End Sub
wc.DownloadStringAsync(url)
Return tcs.Task
End Function
Public Function BeginDownloadString(url As Uri, callback As AsyncCallback, state As Object) As IAsyncResult
Return DownloadStringAsync(url).AsApm(callback, state)
End Function
Public Function EndDownloadString(asyncResult As IAsyncResult) As String
Dim castToTask As Task(Of String) = asyncResult
Return castToTask.Result
End Function
End Module
Public Class AsyncIterators
Private Shared Iterator Function SearchUrl(ae As AsyncEnumerator(Of Boolean), keyword As String, uri As Uri) As IEnumerator(Of Int32)
ae.Result = False
ApmAsyncDownload.BeginDownloadString(uri, ae.End(0, AddressOf ApmAsyncDownload.EndDownloadString), Nothing)
Yield 1
If (ae.IsCanceled()) Then
Return
End If
Try
Dim page As String = ApmAsyncDownload.EndDownloadString(ae.DequeueAsyncResult)
ae.Result = page.Contains(keyword)
Catch ex As AggregateException
End Try
End Function
Public Shared Iterator Function SearchIterator(ae As AsyncEnumerator(Of List(Of String)), keyword As String, urls As List(Of Uri)) As IEnumerator(Of Int32)
ae.Result = New List(Of String)
'Control how many searches are started asynchonously
Dim startSearches = Math.Min(3, urls.Count)
Dim enumerator = urls.GetEnumerator
Dim toBeCompleted = urls.Count
Do Until (toBeCompleted <= 0)
While (startSearches > 0)
If enumerator.MoveNext Then
Dim subAe = New AsyncEnumerator(Of Boolean)()
subAe.SyncContext = Nothing
subAe.BeginExecute(SearchUrl(subAe, keyword, enumerator.Current), ae.End(0, Function(ar As IAsyncResult) As AsyncEnumerator.EndObjectXxx
subAe.EndExecute(ar)
End Function), enumerator.Current)
End If
startSearches = startSearches - 1
End While
'Wait for first async search to complete
Yield 1
toBeCompleted = toBeCompleted - 1
If (ae.IsCanceled()) Then
Exit Do
End If
'Get result of the search and add to results
Dim result = ae.DequeueAsyncResult()
Dim completedAe = AsyncEnumerator(Of Boolean).FromAsyncResult(result)
If (completedAe.EndExecute(result)) Then
Dim uri As Uri = result.AsyncState
ae.Result.Add(uri.OriginalString)
End If
'Start 1 more search
startSearches = startSearches + 1
Loop
End Function
End Class
Module Module1
Sub Main()
Dim searchAe = New AsyncEnumerator(Of List(Of String))()
searchAe.SyncContext = Nothing
Dim urlStrings = New List(Of String) From {"http://www.google.com", "http://www.yahoo.com", "http://www.dogpile.com"}
Dim uris = urlStrings.Select(Function(urlString As String) As Uri
Return New Uri(urlString)
End Function).ToList()
For Each Str As String In searchAe.EndExecute(searchAe.BeginExecute(AsyncIterators.SearchIterator(searchAe, "search", uris), Nothing, Nothing))
Console.WriteLine(Str)
Next
Console.ReadKey()
End Sub
End Module
And I now see what you mean about translating c# lambdas!