In my application, I call a process to update software - which is stored within its own class. Even thou I have am Async/Wait and debug.print is returning a message within frmUpdate.ReportProgress() for some reason the progress bar in the update form is not updating...
Class
Namespace software
Public Class updater
Public Async Function UpdateSoftware(ByVal url As String, ByVal downloadFolder As String) As Tasks.Task(Of Boolean)
Dim progressIndicator = New Progress(Of Integer)(AddressOf ReportProgress)
Await SetProgressBar(progressIndicator, 100)
Return True
End Function
Private Async Function SetProgressBar(ByVal myProgress As IProgress(Of Integer), counter As Integer) As Tasks.Task
myProgress.Report(counter)
End Function
Private Function ReportProgress(ByVal count As Integer)
frmUpdate.ReportProgress(count)
End Function
End Class
End Namespace
Code for the Form Below
Public Class frmUpdate
Private Async Sub btnUpdate_Click(sender As System.Object, e As System.EventArgs) Handles btnUpdate.Click
updater.UpdateSoftware(url, downloadFolder)
End Function
Public Function ReportProgress(ByVal myInt As Integer)
ProgressBar1.Value = myInt
Debug.Print(ProgressBar1.Value)
End Function
End Class
Async and Await do not inherently make things multi-threaded. There's good reason for that. Not all asynchronous tasks are multi-threaded. For instance, if you want to have some task wait until the user clicks a button, you don't need that task to be on it's own thread. That would be very wasteful to have a separate thread just sitting there looping while it waits for the button to be clicked. In a situation like that, you'd just have the click event of the button signal the task to continue/complete. Then, the task can simply block execution until that signal is received. Since it's the case that making all asynchronous tasks multi-threaded is wasteful, starting a task in a new thread is left as a separate optional action.
Simply adding the Async keyword to your method signature does nothing to make your method run on a separate thread. In fact, it technically doesn't even make your method asynchronous. It will only be asynchronous if you call Await inside the method somewhere. So, there is no appreciable difference between your method:
Private Async Function SetProgressBar(ByVal myProgress As IProgress(Of Integer), counter As Integer) As Tasks.Task
myProgress.Report(counter)
End Function
And this:
Private Sub SetProgressBar(ByVal myProgress As IProgress(Of Integer), counter As Integer)
myProgress.Report(counter)
End Sub
Both methods execute immediately when they are called and both block execution in whatever method called them until they are complete. If myProgress, whatever that is, provides an asynchronous version of the Report method (i.e. an equivalent method that returns a Task), then you want to call that and await on it:
Private Async Function SetProgressBar(ByVal myProgress As IProgress(Of Integer), counter As Integer) As Tasks.Task
Await myProgress.ReportAsync(counter)
End Function
If no such asynchronous alternative exists, then you can force it to be asynchronous by starting it in its own thread:
Private Async Function SetProgressBar(ByVal myProgress As IProgress(Of Integer), counter As Integer) As Tasks.Task
Await Task.Run(Sub() myProgress.ReportAsync(counter))
End Function
However, in this case, I'm quite certain, given the name of the method, that it doesn't really need to be asynchronous at all. The real issue is that whatever the long-running work you're doing in UpdateSoftware is, it's not being done with Await, so it's what's blocking when it shouldn't be. But, since you didn't show that code, it's hard to give a good example. Consider something like this:
Public Class updater
Public Async Function UpdateSoftware(ByVal url As String, ByVal downloadFolder As String) As Tasks.Task(Of Boolean)
Await Task.Run(AddressOf LongRunningWork)
Return True
End Function
Private Sub LongRunningWork()
' Do something that takes a while
For i As Integer = 1 to 100
ReportProgress(i)
Thread.Sleep(100)
Next
End Sub
Private Sub ReportProgress(count As Integer)
frmUpdate.BeginInvoke(Sub() frmUpdate.ReportProgress(count))
End Function
End Class
Note that in the ReportProgress method, I have it calling BeginInvoke (though Invoke would work too) on the form to get it to execute that form's method on the UI thread rather than on the task's thread. That's always important to do. Anytime you are updating the UI, you always need to invoke back the the UI thread to do the updating, otherwise you'll get cross-thread exceptions.
Also, you aren't using Await in the event handler either, which you ought to do. Technically, it works either way, but if you start adding exception handling, you'll find out quickly it makes a big difference:
Private Async Sub btnUpdate_Click(sender As System.Object, e As System.EventArgs) Handles btnUpdate.Click
Await updater.UpdateSoftware(url, downloadFolder)
End Function
For more information about Async/Await (Microsoft calls it the Task-based Asynchronous Pattern, or TAP for short), see the documentation. TAP is a really powerful tool. It makes asynchronous code very easy to read and understand. But, despite appearing simple on the surface, it still requires a good understanding of the underlying concepts to use it properly. If you are feeling uncertain about it, you may want to try using the BackgroundWorker component, as I suggested in my answer to your previous question, since it's a little less magical and may be easier for you to see what's happening where and why.
Related
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)
My Winforms application shows an animated gif inside a picturebox while long running operations run. However, it freezes while waiting for the completion of the task:
Public Class MyUserControl
Sub Initialize()
Dim folderscantask = Task.Factory.StartNew(
Function() EwsManagedApiScanFolderHierarchy(),
TaskCreationOptions.LongRunning
)
folderdictask.Wait()
Dim folderscanresult = folderscantask.Result
End Sub
Function EwsManagedApiScanFolderHierarchy() As Dictionary(Of String, String)
'Performs a long, recursive operation involving a
'Microsoft.Exchange.WebServices.Data.ExchangeService object
End Function
End Class
What should I do differently in order to keep PictureBox's animation running?
EDIT
This is a more complete description of my problem, and this time I used Async/Await (since I was taught that Task.Wait() would block the caller thread). Now, animation moves fine until it reaches MyUserControl.BuildFolderMenus() for the first time, then it freezes. Is this inevitable? I mean, don't animations run in a dedicated thread?
Public Class MyForm : Inherits Form
'Form has a PictureBox named PictureBoxWaiting that shows an animated gif
Public Async Sub MyButton_Click(sender as Object, e as EventArgs) Handles MyButton.Click
PictureBoxWaiting.Show()
PictureBoxWaiting.BringToFront()
Await MyUserControl1.Initialize()
PictureBoxWaiting.Hide()
MyUserControl1.Show()
End Sub
End Class
Public Class MyUserControl
Public Async Function Initialize() As Task
Dim folderdic = Await GetFolderHierarchyAsync()
BuildFolderMenus(ToolStripDropDownButtonFolders, folderdic)
End Function
Public Async Function GetFolderHierarchyAsync() As Task(Of Dictionary(Of String, String))
Return Await Task.Factory.StartNew(
Function() EwsManagedApiScanFolderHierarchy(),
TaskCreationOptions.LongRunning
)
End Function
Function EwsManagedApiScanFolderHierarchy() As Dictionary(Of String, String)
'Performs a long, recursive operation involving a
'Microsoft.Exchange.WebServices.Data.ExchangeService object
End Function
Private Sub BuildFolderMenus(menu As ToolStripDropDownItem, dic As Dictionary(Of String, String))
'This reads the dictionary containing the folder hierarchy
'and recursively adds menu items in order that folders´
'subfolders correspond to subitems inside an item
'
'This must run in UI thread since it creates UI controls
End Sub
End Class
You are blocking the UI thread by calling Task.Wait(). You need to use Asunc/Await pattern. For example create a method like this:
Public Async Function MyFunction() as Task
Await Task.Run(Sub()
' Do something non-UI which is time-consuming
' This code runs in another thread without blocking UI
' For example Thread.Sleep(5000)
End Sub)
'The code here runs is UI thread
End Function
And then as the usage:
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Await MyUserControl1.MyFunction()
End Sub
Then you will see, although you have a time-consuming task in MyFunction, but the UI will not be blocked while the task is running.
I am writing a VB form application that redirects the standard output stream of a process and uses it in a UI.
I am having trouble calling methods with parameters that update controls on the form from the OutputHandler sub.
I can call a method without parameters like so
Me.Invoke(New MyDelSub(AddressOf ServerStarted))
Which works fine.
And a bit of googling told me that to call a method with parameters I should do this:
Dim del As JoinDelegate = AddressOf PlayerJoins
del.Invoke(username)
With this delegate and method pair:
Private Delegate Sub JoinDelegate(ByVal username As String)
Private Sub PlayerJoins(ByVal username As String)
PlayersBox.Items.Add(username)
'Do other stuff
End Sub
But this produces an IllegalOperationException the first time the method tries to access a control.
1) Supposing you have a method like this:
Public Sub DoSomething(value1 As String, value2 As String)
MessageBox.Show(String.Format("{0} {1}", value1, value2))
End Sub
You can call it using invoke this way:
Me.Invoke(Sub() DoSomething("Hello", "World!"))
2) If you want to make thread safe call to a control you can write the method this way:
Public Sub AddItemToListBox1(item As String)
If (ListBox1.InvokeRequired) Then
ListBox1.Invoke(Sub() AddItemToListBox1(item))
Else
ListBox1.Items.Add(item)
End If
End Sub
Then it's enough to call it in a the UI thread or in another thread the same way simply:
AddItemToListBox1("some item")
The call would be thread safe.
E.g.
Private Sub SetControlText(control As Control, text As String)
If control.InvokeRequired Then
control.Invoke(New Func(Of Control, String)(AddressOf SetControlText), control, text)
Else
control.Text = text
End If
End Sub
Call that method from any thread.
I have a wcf service reference configured on a client application. It provides a whole series of functions to both retrieve and send data to a web based database. As an example:
Function errorCodesGetAll(ByVal uname As String, ByVal pword As String) As String
and
Function errorCodesGetAllAsync(ByVal uname As String, ByVal pword As String) As System.Threading.Tasks.Task(Of String)
I know that I can populate a rich text box with the first function by using the following code:
RichTextBox1.Text = getCountryList
Private Function getCountryList() As String
Dim svc As New ServiceReference2.ERSAPIServiceClient
svc.Open
Dim str As String = svc.errorCodesGetAll(username, password)
svc.Close()
Return str
End Function
As WCF is still a very new area to me I'm wondering how I would populate the same rich text box but this time using the Async variant of the errorCodesGetAll function?
Thanks for any advice or general pointers as to how the async variants are best used.
Your service will expose a "completed" event as well as the async method, you need to handle that event.
Open the service, wire up the event and call the async method
Private Sub GetCodes()
Dim svc As New ServiceReference2.ERSAPIServiceClient
AddHandler ServiceReference2.errorCodesGetAllCompleted, AddressOf errorCodesGetAllCompletedhandler
ServiceReference2.errorCodesGetAllAsync()
ServiceReference2.Close()
End Sub
Handle the event. This will get called when the service returns. (normally I would not add the "handler" to the end of the method and name it exactly the same as the event, but I thought it might help distinguish the event and the handler)
Private Sub errorCodesGetAllCompletedHandler(ByVal sender As Object, ByVal e As ServiceReference2.errorCodesGetAllEventArgs)
If Not e.Result Is Nothing Then
textbox.text = e.Result
End If
End Sub
Calling the async version of the method is interesting when you're on the UI thread (e.g., on the Click event handler for a button in your form), since that won't "freeze" the UI by blocking the thread, waiting for the networking call to complete.
Since you get the *Async method which returns a Task<T> result, I assume you're using the .NET Framework 4.5. If this is the case, you can take advantage of the Async / Await keywords to call the asynchronous version in a fairly simple way, while still preventing the UI thread from being blocked.
Private Async Sub Button_Click(ByVal sender as Object, ByVal e as EventArgs)
RichTextBox1.Text = Await getCountryList
End Sub
Private Async Function getCountryList() As Task(Of String)
Dim svc As New ServiceReference2.ERSAPIServiceClient
svc.Open
Dim str As String = Await svc.errorCodesGetAllAsync(username, password)
svc.Close()
Return str
End Function
I used to write this:
Private Sub Example()
Static CachedPeople As List(Of MyApp.Person)
If CachedPeople Is Nothing Then
CachedPeople = New List(Of MyApp.Person)
End If
...rest of code...
End Sub
But then wondered if I could reduce this to:
Private Sub Example()
Static CachedPeople As New List(Of MyApp.Person)
...rest of code...
End Sub
The question is, will the "New" bit only be executed once when the function is first executed but in the next call, it will already exist.
Cheers, Rob.
It'll be executed only once and on next function call, it'll reference the same object, as you mentioned. Your first snippet is not thread-safe, by the way. If two threads call your function at the same time, they might end up running the constructor twice, which is not what you want. Using the second snippet relieves you from manually locking and ensuring thread safety, as the compiler generates the appropriate code for you.
Note that if you had declared it as
Static x As List(Of String)
x = New List(Of String)
It would have been recreated each time.