Executing a Callback Method When an Asynchronous Call Completes - vb.net

I am trying to learn more about Asynchronous calls, which are part of the MCSD exam. I have followed all of the examples on the following page successfully: http://msdn.microsoft.com/en-gb/library/2e08f6yc.aspx.
I have created console applications and Winform applications for all of the examples. However, the callback function is never called in the last example (Executing a Callback Method When an Asynchronous Call Completes) if a WinForm application is used. Please see the code below:
Imports System
Imports System.Threading
Imports System.Runtime.InteropServices
Public Class AsyncDemo
' The method to be executed asynchronously.
'
Public Function TestMethod(ByVal callDuration As Integer, _
<Out()> ByRef threadId As Integer) As String
Console.WriteLine("Test method begins.")
Thread.Sleep(callDuration)
threadId = AppDomain.GetCurrentThreadId()
Return "MyCallTime was " + callDuration.ToString()
End Function
End Class
' The delegate must have the same signature as the method
' you want to call asynchronously.
Public Delegate Function AsyncDelegate(ByVal callDuration As Integer, _
<Out()> ByRef threadId As Integer) As String
Public Class AsyncMain
' The asynchronous method puts the thread id here.
Private Shared threadId As Integer
Shared Sub Main()
' Create an instance of the test class.
Dim ad As New AsyncDemo()
' Create the delegate.
Dim dlgt As New AsyncDelegate(AddressOf ad.TestMethod)
' Initiate the asynchronous call.
Dim ar As IAsyncResult = dlgt.BeginInvoke(3000, _
threadId, _
AddressOf CallbackMethod, _
dlgt)
Console.WriteLine("Press Enter to close application.")
Console.ReadLine()
End Sub
' Callback method must have the same signature as the
' AsyncCallback delegate.
Shared Sub CallbackMethod(ByVal ar As IAsyncResult)
' Retrieve the delegate.
Dim dlgt As AsyncDelegate = CType(ar.AsyncState, AsyncDelegate)
' Call EndInvoke to retrieve the results.
Dim ret As String = dlgt.EndInvoke(threadId, ar)
Console.WriteLine("The call executed on thread {0}, with return value ""{1}"".", threadId, ret)
End Sub
End Class
Why is CallbackMethod never reached in a WinForm application? Please note that I understand the difference between a Console application and a WinForm application.

The problem is the Console.ReadLine(). In a WinForms app this call does not block. Instead you can use Thread.Sleep(Timeout.Infinite) or whatever suits your needs best.

Related

How to let the code run smoothly using timers and different threads

I'm trying to prevent the GUI from freezing, because of a low timer interval and too much to process in the Timer.Tick event handler.
I've been googling a while and I understood that I cannot update UI from any other thread other than the UI thread.
So, what about if you are using lots of controls under Timer1.Tick?
How can I update a Label when the data is downloaded with WebClient with a timer, you don't want to lower the interval too much and keep the UI responsive at the same time?
I receiver Cross Thread violation exceptions when I access UI elements, a ListBox1 and a RichTextBox.
What is the correct way to update the UI with a timer and/or a Thread without causing cross threat exceptions?
You have different ways to update UI elements from a Thread other than the UI Thread.
You can use the InvokeRequired/Invoke() pattern (meh), call the asynchronous BeginInvoke() method, Post() to the SynchronizationContext, maybe mixed with an AsyncOperation + AsyncOperationManager (solid BackGroundWorker style), use an async callback etc.
There's also the Progress<T> class and its IProgress<T> interface.
This class provides a quite simplified way to capture the SynchronizationContext where the class object is created and Post() back to the captured execution context.
The Progress<T> delegate created in the UI Thread is called in that context. We just need to pass the Progress<T> delegate and handle the notifications we receive.
You're downloading and handling a string, so your Progress<T> object will be a Progress(Of String): so, it will return a string to you.
The Timer is replaced by a Task that executes your code and also delays its actions by a Interval that you can specify, as with a Timer, here using Task.Delay([Interval]) between each action. There's a StopWatch that measures the time a download actually takes and adjusts the Delay based on the Interval specified (it's not a precision thing, anyway).
▶ In the sample code, the download Task can be started and stopped using the StartDownload() and StopDownload() methods of a helper class.
The StopDownload() method is awaitable, it executes the cancellation of the current tasks and disposes of the disposable objects used.
▶ I've replaced WebClient with HttpClient, it's still quite simple to use, it provides async methods that support a CancellationToken (though a download in progress requires some time to cancel, but it's handled here).
▶ A Button click initializes and starts the timed downloads and another one stops it (but you can call the StopDownload() method when the Form closes, or, well, whenever you need to).
▶ The Progress<T> delegate is just a Lambda here: there's not much to do, just fill a ListBox and scroll a RichTextBox.
You can initialize the helper class object (it's named MyDownloader: of course you will pick another name, this one is ridiculous) and call its StartDownload() method, passing the Progress<T> object, the Uri and the Interval between each download.
Private downloader As MyDownloader = Nothing
Private Sub btnStartDownload_Click(sender As Object, e As EventArgs) Handles btnStartDownload.Click
Dim progress = New Progress(Of String)(
Sub(data)
' We're on the UI Thread here
ListBox1.Items.Clear()
ListBox1.Items.AddRange(Split(data, vbLf))
RichTextBox1.SelectionStart = RichTextBox1.TextLength
End Sub)
Dim url As Uri = New Uri("https://SomeAddress.com")
downloader = New MyDownloader()
' Download from url every 1 second and report back to the progress delegate
downloader.StartDownload(progress, url, 1)
Private Async Sub btnStopDownload_Click(sender As Object, e As EventArgs) Handles btnStopDownload.Click
Await downloader.StopDownload()
End Sub
The helper class:
Imports System.Diagnostics
Imports System.Net
Imports System.Net.Http
Imports System.Text.RegularExpressions
Public Class MyDownloader
Implements IDisposable
Private Shared client As New HttpClient()
Private cts As CancellationTokenSource = Nothing
Private interval As Integer = 0
Private disposed As Boolean
Public Sub StartDownload(progress As IProgress(Of String), url As Uri, intervalSeconds As Integer)
interval = intervalSeconds * 1000
Task.Run(Function() DownloadAsync(progress, url, cts.Token))
End Sub
Private Async Function DownloadAsync(progress As IProgress(Of String), url As Uri, token As CancellationToken) As Task
token.ThrowIfCancellationRequested()
Dim responseData As String = String.Empty
Dim pattern As String = "<(?:[^>=]|='[^']*'|=""[^""]*""|=[^'""][^\s>]*)*>"
Dim downloadTimeWatch As Stopwatch = New Stopwatch()
downloadTimeWatch.Start()
Do
Try
Using response = Await client.GetAsync(url, HttpCompletionOption.ResponseContentRead, token)
responseData = Await response.Content.ReadAsStringAsync()
responseData = WebUtility.HtmlDecode(Regex.Replace(responseData, pattern, ""))
End Using
progress.Report(responseData)
Dim delay = interval - CInt(downloadTimeWatch.ElapsedMilliseconds)
Await Task.Delay(If(delay <= 0, 10, delay), token)
downloadTimeWatch.Restart()
Catch tcEx As TaskCanceledException
' Don't care - catch a cancellation request
Debug.Print(tcEx.Message)
Catch wEx As WebException
' Internet connection failed? Internal server error? See what to do
Debug.Print(wEx.Message)
End Try
Loop
End Function
Public Async Function StopDownload() As Task
Try
cts.Cancel()
client?.CancelPendingRequests()
Await Task.Delay(interval)
Finally
client?.Dispose()
cts?.Dispose()
End Try
End Function
Protected Overridable Sub Dispose(disposing As Boolean)
If Not disposed AndAlso disposing Then
client?.Dispose()
client = Nothing
End If
disposed = True
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
End Sub
End Class
Your listbox and richtextbox accesses must run on the UI thread. The easiest way to do it is like this.
Me.Invoke(Sub()
ListBox1.Items.Clear()
ListBox1.Items.AddRange(Split(clientdecode, vbLf))
RichTextBox1.SelectionStart() = RichTextBox1.TextLength
RichTextBox1.ScrollToCaret()
End Sub)

AsyncCallback of BackgroundWorker Webservice reference Function

I am trying to build a new class from where I can call functions with a background worker from a web service in a windows Phone 8.1 silverlight app.
calling page
LoginPage.xaml.vb
Partial Public Class LoginPage
Private sub Logon()
WebServiceHelper.a(WebServiceHelper.Functions.Logon)
LoginFinish()
End Sub
End Class
WebserviceHelper
WebServiceHelper.vb
Public Class WebServiceHelper
Public Shared Sub a(ByVal _Task As Functions)
If _Task = Functions.Logon Then
_Service.BeginLogonWindowsPhone(usr, pass, New AsyncCallback(AddressOf ResultBackGroundTask), result)
End If
End Sub
Public Shared Sub ResultBackGroundTask(ByVal result As Object)
If _Task = Functions.Logon Then
ResultObject = result
End If
End Sub
End Class
The problem is when I call webservice.a() the AsyncCallback ResultBackGroundTask doesn’t fire in time. Instead LoginFinish is called resulting in an error because the resultobject isn’t initialized yet.
I've tried:
Task.Factory.FromAsync(_Service.BeginLogonWindowsPhone, ResultBackGroundTask, usr, pass, Nothing)
But I get an error:
Argument not specified for parameter 'asyncState' of
'Public Function BeginLogonWindowsPhone(userName As String, password As String, callback As System.AsyncCallback, asyncState As Object) As System.IAsyncResult'.
The function I try to call is:
<System.ServiceModel.OperationContractAttribute(AsyncPattern:=true, Action:="http://test.com/LogonWindowsPhone", ReplyAction:="*"), _
System.ServiceModel.XmlSerializerFormatAttribute(SupportFaults:=true)> _
Function BeginLogonWindowsPhone(ByVal userName As String, ByVal password As String, ByVal callback As System.AsyncCallback, ByVal asyncState As Object) As System.IAsyncResult
This function is automatically generated in the webservice reference.
I use the Webservice to connect with the clients database, for security reasons.

Service Does Not Launch Console Application

TLDR: My console application runs if I click on it manually however it does not run when called by service while using StartProcess or Shell.
Could this be a permission issue?
Details:
I've written a small windows service application that checks a remote location folder for a product key and updates the local machine if the product key is different than what is entered. I thought this would be a simple project for my first foray into windows services.
The service runs using a timer every hour (for testing the interval is every 30 seconds). Originally the service would perform the updating but I ran into a more complicated issue accessing the UNC path (I would have to use Impersonation Classes).
So while testing the code in a console version of the application, I noticed it was able to access the network location without supplying credentials. So I rewrote the service so it calls the console application instead. But no matter how I write it I can't get the console application to launch from the service.
If anymore information is needed please feel free to ask!
Thank you for your time.
Damien hit the nail on the head, the issue is that when you run the console app, you are running it as you, which has access to the UNC. When the service runs your console app, it is running it as the user you tied to the service. You have a couple options:
1: Setup the service to run as a user with access to the UNC
2: Use Impersonation to connect as the user you want to.
Either way, running it from a console application instead of your service is useless.
I have included a class I created that makes impersonation easy:
Imports System.Security.Principal
Imports System.Runtime.InteropServices
''' <summary>
''' Used to temporarily impersonate another user
''' Must use a USING block or dispose of instance to undo impersonation
''' </summary>
''' <remarks></remarks>
Public Class ImpersonateFNS
Implements IDisposable
Declare Function LogonUserA Lib "advapi32.dll" (ByVal lpszUsername As String, ByVal lpszDomain As String, ByVal lpszPassword As String, _
ByVal dwLogonType As Integer, ByVal dwLogonProvider As Integer, ByRef phToken As IntPtr) As Integer
Private _impersonatedUser As WindowsImpersonationContext = Nothing
Private _loggedOn As Boolean = False
''' <summary>
''' Should call this in a USING block OR, make sure you dispose this object when done impersonating
''' </summary>
Public Sub New(ByVal Domain As String, ByVal UserName As String, ByVal Password As String)
Dim token As IntPtr = IntPtr.Zero
'If (LogonUserA(UserName, Domain, Password, 2, 0, token) <> 0) Then
If (LogonUserA(UserName, Domain, Password, 9, 0, token) <> 0) Then
_loggedOn = True
Dim newIdentity As WindowsIdentity = New WindowsIdentity(token)
_impersonatedUser = newIdentity.Impersonate()
Else
Dim ret As Integer = Marshal.GetLastWin32Error()
'Console.WriteLine("LogonUser failed with error code : {0}", ret)
'Throw New System.ComponentModel.Win32Exception(String.Format("LogonUser failed with error code : {0}", ret))
Throw New Security.SecurityException(String.Format("LogonUser failed with error code : {0}", ret))
End If
End Sub
Private ReadOnly Property LoggedOn As Boolean
Get
Return _loggedOn
End Get
End Property
#Region "IDisposable Support"
Private disposedValue As Boolean ' To detect redundant calls
' IDisposable
Protected Overridable Sub Dispose(disposing As Boolean)
If Not Me.disposedValue Then
If disposing Then
' TODO: dispose managed state (managed objects).
End If
If _impersonatedUser IsNot Nothing Then
_impersonatedUser.Undo()
End If
' TODO: free unmanaged resources (unmanaged objects) and override Finalize() below.
' TODO: set large fields to null.
End If
Me.disposedValue = True
End Sub
' TODO: override Finalize() only if Dispose(ByVal disposing As Boolean) above has code to free unmanaged resources.
'Protected Overrides Sub Finalize()
' ' Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above.
' Dispose(False)
' MyBase.Finalize()
'End Sub
' This code added by Visual Basic to correctly implement the disposable pattern.
Public Sub Dispose() Implements IDisposable.Dispose
' Do not change this code. Put cleanup code in Dispose(disposing As Boolean) above.
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
#End Region
End Class
Create the class as shown above and then call is in a USING block so it will undo the impersonation when you are done doing your business. like so:
Using x as New ImpersonationFNF("MyDomain", "User","Password")
'Copy, read, whatever the stuff you need to do here
End Using

How to pass multiple parameters in thread in VB

I'm looking to pass two or more parameters to a thread in VB 2008.
The following method (modified) works fine without parameters, and my status bar gets updated very cool-y.
But I can't seem to make it work with one, two or more parameters.
This is the pseudo code of what I'm thinking should happen when the button is pressed:
Private Sub Btn_Click()
Dim evaluator As New Thread(AddressOf Me.testthread(goodList, 1))
evaluator.Start()
Exit Sub
This is the testthread method:
Private Sub testthread(ByRef goodList As List(Of OneItem), ByVal coolvalue As Integer)
StatusProgressBar.Maximum = 100000
While (coolvalue < 100000)
coolvalue = coolvalue + 1
StatusProgressBar.Value = coolvalue
lblPercent.Text = coolvalue & "%"
Me.StatusProgressBar.Refresh()
End While
End Sub
First of all: AddressOf just gets the delegate to a function - you cannot specify anything else (i.e. capture any variables).
Now, you can start up a thread in two possible ways.
Pass an Action in the constructor and just Start() the thread.
Pass a ParameterizedThreadStart and forward one extra object argument to the method pointed to when calling .Start(parameter)
I consider the latter option an anachronism from pre-generic, pre-lambda times - which have ended at the latest with VB10.
You could use that crude method and create a list or structure which you pass to your threading code as this single object parameter, but since we now have closures, you can just create the thread on an anonymous Sub that knows all necessary variables by itself (which is work performed for you by the compiler).
Soo ...
Dim Evaluator = New Thread(Sub() Me.TestThread(goodList, 1))
It's really just that ;)
Something like this (I'm not a VB programmer)
Public Class MyParameters
public Name As String
public Number As Integer
End Class
newThread as thread = new Thread( AddressOf DoWork)
Dim parameters As New MyParameters
parameters.Name = "Arne"
newThread.Start(parameters);
public shared sub DoWork(byval data as object)
{
dim parameters = CType(data, Parameters)
}
Dim evaluator As New Thread(Sub() Me.testthread(goodList, 1))
With evaluator
.IsBackground = True ' not necessary...
.Start()
End With
Well, the straightforward method is to create an appropriate class/structure which holds all your parameter values and pass that to the thread.
Another solution in VB10 is to use the fact that lambdas create a closure, which basically means the compiler doing the above automatically for you:
Dim evaluator As New Thread(Sub()
testthread(goodList, 1)
End Sub)
In addition to what Dario stated about the Delegates you could execute a delegate with several parameters:
Predefine your delegate:
Private Delegate Sub TestThreadDelegate(ByRef goodList As List(Of String), ByVal coolvalue As Integer)
Get a handle to the delegate, create parameters in an array, DynamicInvoke on the Delegate:
Dim tester As TestThreadDelegate = AddressOf Me.testthread
Dim params(1) As Object
params(0) = New List(Of String)
params(1) = 0
tester.DynamicInvoke(params)
Just create a class or structure that has two members, one List(Of OneItem) and the other Integer and send in an instance of that class.
Edit: Sorry, missed that you had problems with one parameter as well. Just look at Thread Constructor (ParameterizedThreadStart) and that page includes a simple sample.
Pass multiple parameter for VB.NET 3.5
Public Class MyWork
Public Structure thread_Data
Dim TCPIPAddr As String
Dim TCPIPPort As Integer
End Structure
Dim STthread_Data As thread_Data
STthread_Data.TCPIPAddr = "192.168.2.2"
STthread_Data.TCPIPPort = 80
Dim multiThread As Thread = New Thread(AddressOf testthread)
multiThread.SetApartmentState(ApartmentState.MTA)
multiThread.Start(STthread_Data)
Private Function testthread(ByVal STthread_Data As thread_Data)
Dim IPaddr as string = STthread_Data.TCPIPAddr
Dim IPport as integer = STthread_Data.TCPIPPort
'Your work'
End Function
End Class
I think this will help you...
Creating Threads and Passing Data at Start Time!
Imports System.Threading
' The ThreadWithState class contains the information needed for
' a task, and the method that executes the task.
Public Class ThreadWithState
' State information used in the task.
Private boilerplate As String
Private value As Integer
' The constructor obtains the state information.
Public Sub New(text As String, number As Integer)
boilerplate = text
value = number
End Sub
' The thread procedure performs the task, such as formatting
' and printing a document.
Public Sub ThreadProc()
Console.WriteLine(boilerplate, value)
End Sub
End Class
' Entry point for the example.
'
Public Class Example
Public Shared Sub Main()
' Supply the state information required by the task.
Dim tws As New ThreadWithState( _
"This report displays the number {0}.", 42)
' Create a thread to execute the task, and then
' start the thread.
Dim t As New Thread(New ThreadStart(AddressOf tws.ThreadProc))
t.Start()
Console.WriteLine("Main thread does some work, then waits.")
t.Join()
Console.WriteLine( _
"Independent task has completed main thread ends.")
End Sub
End Class
' The example displays the following output:
' Main thread does some work, then waits.
' This report displays the number 42.
' Independent task has completed; main thread ends.
With VB 14, you can do the following with Tuples:
Shared Sub _runner(data as (goodList As List(Of OneItem), coolvalue As Integer))
Console.WriteLine($"goodList: {data.goodList}")
Console.WriteLine($"coolvalue: {data.coolvalue}")
' do stuff...
End Sub
Dim thr As New Thread(AddressOf _runner)
thr.Start((myGoodList, cval))

how to know when a work in a thread is complete?

I need to create multiple threads when a button is clicked and i've done that with this:
Dim myThread As New Threading.Thread(AddressOf getFile)
myThread.IsBackground = True
myThread.Start()
but i need to update a picture box with the downloaded file, buy if i set an event in the function getFile and raise it to notify that the files was downloaded and then update the picturebox.
Use an AsyncResult, and either check it periodically for completion, or provide a delegate to be called when the thread has completed its work.
A complete example in VB can be found here.
You need to make use of MethodInvoker deligate.
Public Sub GetFile()
If Me.InvokeRequired Then
Me.Invoke(New MethodInvoker(GetFile))
End If
End Sub
Now you can handle any event in your specified class.
You can achive that using the Asyncallback, ...
Dim sinctotal As New Del_sinc(AddressOf sincronizar)
Dim ar As IAsyncResult = sinctotal.BeginInvoke(_funcion, type, New AsyncCallback(AddressOf SincEnd), cookieobj)
The cookieobj is this
Class Cookie
Public id As String
Public AsyncDelegate As [Delegate]
Sub New(ByVal id As String, ByVal asyncDelegate As [Delegate])
Me.id = id
Me.AsyncDelegate = asyncDelegate
End Sub
End Class
When the delegate finish it will call the funcion Sincend (in this example), then you could use a event to update your picture.
Hope this helps!