I'm a beginner using async in VB.NET. I read online help but some things aren't clear.
I try to use tweetinvi library
I got this:
Namespace tweet_invi
Class twitter_call
Public Shared Async Function twitter_get_user_info_from_id(id As Long) As Task
Dim userClient = New TwitterClient(ConfigurationManager.AppSettings("consumerKey"), ConfigurationManager.AppSettings("consumerSecret"), ConfigurationManager.AppSettings("accessToken"), ConfigurationManager.AppSettings("accessTokenSecret"))
Dim tweetinviUser = Await userClient.Users.GetUserAsync(id)
Dim description As String = tweetinviUser.Description
End Function
End Class
End Namespace
And the module from where i would launch this async function
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
Dim toto As Long = 1311275527223812096
Dim result = tweet_invi.twitter_call.twitter_get_user_info_from_id(toto)
End Sub
My issue: result is a task. How do i have to get the value of description?
You can see it in the code you posted. The second line of that method does it. You use the Await operator to await the completion of the Task.
That said, there is no result to get anyway. If you have a synchronous Sub then that becomes an asynchronous Function that returns a Task. In both cases, there is no actual value to get out of the method. As such, awaiting such a method doesn't return anything. If you have a synchronous Function with a return type of T then that becomes an asynchronous Function that returns a Task(Of T). Awaiting that gives you a result of type T.
If you had these methods:
Private Sub DoSomething()
'...
End Sub
Private Function GetSomething() As SomeType
'...
End Function
then you'd call them like this:
DoSomething()
Dim someValue As SomeType = GetSomething()
If you had these methods:
Private Async Function DoSomethingAsync() As Task
'...
End Function
Private Async Function GetSomethingAsync() As Task(Of SomeType)
'...
End Function
then you'd call them like this:
Await DoSomethingAsync()
Dim someValue As SomeType = Await GetSomethingAsync()
VB actually does support Async Sub but the ONLY time you should ever us it is for event handlers, which MUST be declared Sub, i.e. you cannot handle an event with a Function. Also, any method in which you want to use the Await operator must be declared Async. Together, that means that you must declare the Click event handler of your Button as Async Sub and then you can await an asynchronous method in it:
Private Async Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
Dim toto As Long = 1311275527223812096
Await tweet_invi.twitter_call.twitter_get_user_info_from_id(toto)
End Sub
With regards to the code you posted, that twitter_get_user_info_from_id method is useless. It declares and sets some local variables but does nothing with the data it gets. I suspect that that method should be like this:
Namespace tweet_invi
Class twitter_call
Public Shared Async Function twitter_get_user_info_from_id(id As Long) As Task(Of String)
Dim userClient = New TwitterClient(ConfigurationManager.AppSettings("consumerKey"), ConfigurationManager.AppSettings("consumerSecret"), ConfigurationManager.AppSettings("accessToken"), ConfigurationManager.AppSettings("accessTokenSecret"))
Dim tweetinviUser = Await userClient.Users.GetUserAsync(id)
Dim description As String = tweetinviUser.Description
Return description
End Function
End Class
End Namespace
and then you would call it like this:
Private Async Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
Dim toto As Long = 1311275527223812096
Dim userInfo = Await tweet_invi.twitter_call.twitter_get_user_info_from_id(toto)
'...
End Sub
Related
I'm trying to create a bot that place multiples orders on Binance and I need a fast coin price parsing.
I'm parsing the code using this code
Dim price as decimal
Private Async Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
Timer1.Stop()
Dim downloadTasks As New List(Of Task(Of String))
Dim dogebusd = wc1.DownloadStringTaskAsync("https://api.binance.com/api/v1/ticker/price?symbol=DOGEBUSD")
downloadTasks.Add(dogebusd)
Await Task.WhenAll(downloadTasks)
Dim d = JsonConvert.DeserializeObject(Of Dictionary(Of String, String))(dogebusd.Result)
Dim PREZZO As Decimal = CDec(Val((d("price")).ToString))
price = CDec((PREZZO))
Timer1.Start()
End Sub
but when the price is dumping or pumping really fast, even a 10 ms or 100ms timer tick is not that efficient.
I wonder if this is the fastest way possible or if I can improve the code.
Thanks
following Jimi suggestions:
Dim price As Decimal
Private ReadOnly _timer As New PeriodicTimer(TimeSpan.FromSeconds(1))
Private Async Sub Timer1_Tick(sender As Object, e As EventArgs) Handles _timer.Tick
timer1.stop
Dim dogebusdTask = HttpClient.GetStringAsync("https://api.binance.com/api/v1/ticker/price?symbol=DOGEBUSD")
Await dogebusdTask
Dim d = JsonConvert.DeserializeObject(Of Dictionary(Of String, String))(dogebusdTask.Result)
If Decimal.TryParse(d("price"), NumberStyles.Number, CultureInfo.InvariantCulture, price) Then
_timer.Start()
End If
End Sub
An example, using the .NET 6+ awaitable PeriodicTimer and System.Text.Json to parse the resulting stream returned by HttpClient's GetStreamAsync()
The PeriodicTimer can be quite efficient in contexts like this, since it tries to keep up with the interval you have provided. Since the internal procedure takes time to execute - the HttpClient.GetStreamAsync() takes an undetermined amount of time to execute and return a result - the Timer keeps track of the actual elapsed time and tries to keep ticking steadily (unless the procedure takes more than the specified Interval, then you skip a beat, of course)
Note: as usual, when dealing with Tasks that accept a CancellationToken, you should run this code without a Debugger (CTRL+F5) or configure the Debugger to not halt on TaskCanceledException or OperationCanceledException, otherwise it handles the (ignorable) exception before you do
Imports System.Globalization
Imports System.Net.Http
Imports System.Text.Json
Imports System.Threading
Private Shared ReadOnly m_Client As New HttpClient()
Private timerCts As CancellationTokenSource = Nothing
Private m_PeriodicTimer As PeriodicTimer = Nothing
Private m_PeriodicTimerInterval As Integer = 500
Private currentPrice As Decimal = 0.0D
Private Async Function StartPriceLookupTimer(token As CancellationToken, timerInterval As Integer) As Task
If token.IsCancellationRequested Then Return
Dim lookupAddress = "https://api.binance.com/api/v1/ticker/price?symbol=DOGEBUSD"
m_PeriodicTimer = New PeriodicTimer(TimeSpan.FromMilliseconds(timerInterval))
Try
While Await m_PeriodicTimer.WaitForNextTickAsync(token)
Dim jsonStream = Await m_Client.GetStreamAsync(lookupAddress, token)
Try
Dim prop = (Await JsonDocument.ParseAsync(jsonStream, Nothing, token)).RootElement.GetProperty("price")
Dim strPrice = prop.GetString()
If Decimal.TryParse(strPrice, CultureInfo.InvariantCulture, currentPrice) Then
' Do whatever you need to do with the parsed value
' E.g., assign it to a TextBox for presentation,
' using the current Culture format, since strPrice contains the original format
[Some TextBox].Text = currentPrice.ToString() ' <= UI Thread here
End If
Catch knfex As KeyNotFoundException
Debug.WriteLine("The JSON property was not found")
Catch tcex As TaskCanceledException
Debug.WriteLine("The lookup procedure was canceled")
End Try
End While
Catch tcex As TaskCanceledException
Debug.WriteLine("The lookup procedure was canceled")
End Try
End Function
Private Sub StopPriceLookupTimer()
timerCts?.Cancel()
m_PeriodicTimer?.Dispose()
timerCts?.Dispose()
timerCts = Nothing
End Sub
How to start this lookup procedure, calling the StartPriceLookupTimer() method?
You could use a Button, making its Click handler async:
Private Async Sub SomeButton_Click(sender As Object, e As EventArgs) Handles SomeButton.Click
If timerCts Is Nothing Then
timerCts = New CancellationTokenSource()
Await StartPriceLookupTimer(timerCts.Token, m_PeriodicTimerInterval)
Else
Debug.WriteLine("Lookup Timer already started")
End If
End Sub
Or make the Form's Shown Handler / OnShown() method override async and start the procedure when the Form is first presented:
Protected Overrides Async Sub OnShown(e As EventArgs)
MyBase.OnShown(e)
timerCts = New CancellationTokenSource()
Await StartPriceLookupTimer(timerCts.Token, m_PeriodicTimerInterval)
End Sub
To stop the lookup procedure, call the StopPriceLookupTimer() method when needed, e.g., using another Button or in the Form.Closed handler / OnFormClosed() override
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.
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 have been reading for days, and I can't quite figure out what I am supposed to do here. I am actually a C# developer and programming in VB.NET can be a little confusing at times. That aside, I am trying to implement Async calls from a WCF in a new project that I am creating here. I've tried it a few different ways with results, but I want to make sure what I am doing is proper.
From what I have read, you should never return a void in an async method, so I am trying my hardest to avoid it. All of that aside, I guess I just want to know if this is a valid way to go about building this page. It will be calling more than one method from the WCF as I build it out.
Public Class _DefaultReservation
Inherits System.Web.UI.Page
Dim wcfReservation As WCFReservation.WDReservationClient
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
SetupPage()
End Sub
Private Async Sub SetupPage()
wcfReservation = DirectCast(Master, LoggedInMaster).wcfReservation
Dim resData As String = Await wcfReservation.GetDataAsync(123)
Response.Write(resData)
End Sub
End Class
I guess what is confusing is if I put that code inside of an async function and return the task, I would have to mark the page_load handler as async as well. It doesn't seem right doing it this way? By doing that it seems like an async function is calling an async function. But the way I am doing it here, my async function returns void, and that is supposed to be avoided. I can post an example of the other way too if needed. Thank you!!
Edit: Does this work better?
Imports System.Threading.Tasks
Public Class _DefaultReservation
Inherits System.Web.UI.Page
Dim wcfReservation As WCFReservation.WDReservationClient
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
SetupPage()
End Sub
Private Async Sub SetupPage()
wcfReservation = DirectCast(Master, LoggedInMaster).wcfReservation
Dim getDataResult = Await GetDataAsync()
Response.Write(getDataResult)
End Sub
Private Function GetDataAsync() As Task(Of String)
Return wcfReservation.GetDataAsync(123)
End Function
End Class
EDIT 3:
Imports System.Threading.Tasks
Public Class _DefaultReservation
Inherits System.Web.UI.Page
Dim wcfReservation As WCFReservation.WDReservationClient
Protected Async Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
wcfReservation = DirectCast(Master, LoggedInMaster).wcfReservation
Dim result As String = Await wcfReservation.GetDataAsync(1234)
Response.Write(result)
End Sub
End Class
It is true you should avoid async void. The exception for this guideline is when you have async event handlers.
Such as Page_Load.
For more information about this guideline, see my MSDN article on Best Practices in Asynchronous Programming.
It doesn't seem right doing it this way? By doing that it seems like an async function is calling an async function.
That's perfectly correct. Async code will "grow" through your code base. The correct solution is to make SetupPage a Task-returning function and await it in Page_Load (which is an async void/Sub).
Edit:
Imports System.Threading.Tasks
Public Class _DefaultReservation Inherits System.Web.UI.Page
Dim wcfReservation As WCFReservation.WDReservationClient
Protected Async Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim getDataResult = Await GetDataAsync()
Response.Write(getDataResult)
End Sub
Public Function GetDataAsync() As Task(Of String)
wcfReservation = DirectCast(Master, LoggedInMaster).wcfReservation
Return wcfReservation.GetDataAsync(123)
End Function
End Class
As luck would have it, I just posted about Async Sub yesterday. Long story short, in your case if you use Async Sub, control will revert to the calling method as soon as the await is encountered. As a result, your page_Load handler will end before the SetupPage is complete. If you want to have Page_Load wait until SetupPage has asynchronously completed, you need to change the SetupPage to be a function returning Task and then Await SetupPage in Page_Load (causing Page_Load) to be Async.
Async Sub is valid on event handlers. Lucian discusses this at some length in his recent Async Patterns post. You may also want to check out the Async talk from ASP.Net Conf last year for special considerations on using Async with ASP.Net/WCF.
Relevant to Silverlight 5 / Async CTP
I want to create an asynchronous function that initiates a layout update and then Awaits for the layout update to complete. Something like:
Private Async Function UpdateLayoutRoot() As Task
LayoutRoot.UpdateLayout()
Await LayoutRoot.LayoutUpdated <--- (NOT valid but shows desired outcome)
End Function
How can this be done? More generally, how can you use Await to wait for existing events?
One way to accomplish this is to await on a TaskCompletionSource that is set inside the event. I don't know VB.NET, hopefully you can understand it from C#:
// The type and value returned by the TaskCompletionSource
// doesn't matter, so I just picked int.
TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
// The delegate sets the TaskCompletionSource -- the result value
// doesn't matter, we only care about setting it. Keep hold of
// the delegate so it can be removed later.
EventHandler d = (o, e) => { tcs.TrySetResult(1); };
LayoutRoot.LayoutUpdate += d;
try
{
LayoutRoot.UpdateLayout();
await tcs.Task;
}
finally
{
// Don't leak the delegate!
LayoutRoot.LayoutUpdate -= d;
}
Thanks Cory! Your suggestion to use TaskCompletionSource is just what I needed. I've combined the use of a TaskCompletionSource with the Lucian Wischik's Async CTP specification to develop a pair of generic Silverlight 5 classes that can be used to Await any CLR, or routed event. Only the Async CTP (AsyncCtpLibrary_Silverlight) is required (The formidable Rx library is not needed). Here are the two classes:
Public Class AwaitableEvent(Of TResult)
Private eta As EventTaskAwaiter(Of TResult) = Nothing
Sub New(ByVal Sender As Object, ByVal EventName As String)
eta = New EventTaskAwaiter(Of TResult)
Dim ei as EventInfo = Sender.GetType.GetEvent(EventName)
Dim d = [Delegate].CreateDelegate(ei.EventHandlerType,
Me, "EventCompletedHandler", True, True)
ei.AddEventHandler(Sender, d)
End Sub
Public Function GetAwaiter() As EventTaskAwaiter(Of TResult)
Return eta
End Function
Private Sub EventCompletedHandler(ByVal sender As Object, ByVal e As TResult)
eta.tcs.TrySetResult(e)
End Sub
End Class
Public Class EventTaskAwaiter(Of TResult)
Friend tcs As New TaskCompletionSource(Of TResult)
Public ReadOnly Property IsCompleted As Boolean
Get
Return tcs.Task.IsCompleted
End Get
End Property
Sub OnCompleted(r As Action)
Dim sc = SynchronizationContext.Current
If sc Is Nothing Then
tcs.Task.ContinueWith(Sub() r())
Else
tcs.Task.ContinueWith(Sub() sc.Post(Sub() r(), Nothing))
End If
End Sub
Function GetResult() As TResult
If tcs.Task.IsCanceled Then Throw New TaskCanceledException(tcs.Task)
If tcs.Task.IsFaulted Then Throw tcs.Task.Exception.InnerException
Return tcs.Task.Result
End Function
End Class
Here's an example of using the AwaitableEvent class to Await mouse, keyboard and timer events.
Private Sub AECaller()
GetMouseButtonAsync()
MessageBox.Show("After Await mouse button event")
GetKeyAsync()
MessageBox.Show("After Await key event")
GetTimerAsync()
MessageBox.Show("After Await timer")
End Sub
Private Async Sub GetMouseButtonAsync()
Dim ae As New AwaitableEvent(Of MouseButtonEventArgs)(LayoutRoot, "MouseLeftButtonDown")
Dim e = Await ae
MessageBox.Show(String.Format("Clicked {0} at {1},{2}",
e.OriginalSource.ToString,
e.GetPosition(LayoutRoot).X,
e.GetPosition(LayoutRoot).Y))
End Sub
Private Async Sub GetKeyAsync()
Dim ae As New AwaitableEvent(Of KeyEventArgs)(LayoutRoot, "KeyDown")
Dim e = Await ae
MessageBox.Show(String.Format("Key {0} was pressed", e.Key.ToString))
End Sub
Private Async Sub GetTimerAsync()
Dim StopWatch As New DispatcherTimer
StopWatch.Interval = New TimeSpan(TimeSpan.TicksPerSecond * 6)
Dim ae As New AwaitableEvent(Of EventArgs)(StopWatch, "Tick")
StopWatch.Start()
Await ae
MessageBox.Show(String.Format("It's {0}seconds later!", StopWatch.Interval.TotalSeconds))
StopWatch.Stop()
End Sub
As expected the Await statement returns control to the calling function immediately. When the events are subsequently completed, Await assigns the result (the event args appropriate for the event being monitored) and the remaining code in the asynchronous method is then run.