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
Related
Works as expected in the console application (I converted it from a C# YouTube tutorial for reasons I won't bore you with), but hangs with no exception thrown in the desktop app when calling GetAsync.
`Imports System
Imports System.Net.Http
Module Moduke1
Sub Main()
Dim strContent As Task(Of String) = GetRequest("http://www.google.com.pk")
Console.WriteLine(strContent.Result)
Console.ReadKey()
End Sub
Async Function GetRequest(url As String) As Task(Of String)
Using client As New HttpClient()
Using response As HttpResponseMessage = Await client.GetAsync(url)
Using content As HttpContent = response.Content
Dim myContent As String = Await content.ReadAsStringAsync()
Return myContent
End Using
End Using
End Using
End Function
End Module`
That works, but the following does not. Probably a rookie error, although I'm not really a rookie - never used System.Net.Http until now and I've been all round the houses with this one.
The following hangs at the call to GetAsync...
`Imports System
Imports System.Net.Http
Public Class HTTP_Test_One
Public Sub HTTP_Test_One_Load(sender As Object, e As EventArgs) Handles MyBase.Load
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim strContent As Task(Of String) = GetRequest("http://www.google.com.pk")
txtResults.Text = strContent.Result
End Sub
Async Function GetRequest(url As String) As Task(Of String)
Using client As New HttpClient()
Using response As HttpResponseMessage = Await client.GetAsync(url)
Using content As HttpContent = response.Content
Dim myContent As String = Await content.ReadAsStringAsync()
Return myContent
End Using
End Using
End Using
End Function
End Class`
I'm not 100% on this, and suspect I may get corrected by people more in the know than I, maybe even the why. Looks to me that awaiting the Result of your task is jamming up, why not in the Console app, my guess is because it doesn't have the overhead of UI thread, or being triggered by an event. Just by reworking your button event handler, I at least get the desired response.
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim strContent As String = Await GetRequest("http://www.google.com.pk")
txtResults.Text = strContent
End Sub
Note I've changed the Event to Async, which means I can just await the response from GetRequest() rather than looking at the task result
Blocking on async code in the UI thread is likely to lead to a deadlock. This has been discussed in a number of async-related resources; my go-to is the series of posts by Stephen Cleary, and he discusses this specific issue in https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html with some additional detail on why blocking can lead to a deadlock.
The right way to do this is to make your event handler Async and then Await the result within it, i.e.
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim content = Await GetRequest("http://www.google.com.pk")
txtResults.Text = content
End Sub
Note that Async Sub is generally recommended against due to issues with processing errors, but event handlers are the exception---Async Sub is designed specifically for UI event handlers.
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)
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.
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 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!