Why Async function returning System.Threading.Tasks.Task`1[System.String]? - vb.net

I have a VB.NET function as below:
Public Shared Async Function GetIdDoc() As Task(Of String)
Dim result As String = ""
'Dim Uri As String = "http://localhost:53917/api/Documenti/GetNextIdDocumenti"
Dim Uri As String = apiUri & ApiEndPoints.GetNextIdDocumenti
Using client = New HttpClient()
Using response = Await client.GetAsync(Uri)
If response.IsSuccessStatusCode Then
Dim DocumentiIDJsonString = Await response.Content.ReadAsStringAsync()
result = DocumentiIDJsonString.ToString()
End If
End Using
End Using
Return result
End Function
I'm trying to return the Document ID from the DB but I'm getting
System.Threading.Tasks.Task`1[System.String]
Where actually it should return "2". Please help me on this: what am I doing wrong with this function?
Update
here is the function called:
txtIDDoc_Detail.Text = ApiData.GetIdDoc().ToString()
But inside the textbox I'm getting the above text. thanks.

I'm from C# but should work the same. In newer .Net Versions (>= 4.5) async/await is implemented. So if a method is marked as async and returns a Task (which should always be the case), you need to await it. This implicate that you have to mark your Method as async too. So your call should look like this:
txtIDDoc_Detail.Text = await ApiData.GetIdDoc();
The await waits till the long running Task is ready and returns it's inner value. All async Methods should return Task. If the Method is void it would be Task. Else it could be Task<int> or any other type. So await it and you can keep running ;)

#Sebi gives a great explanation of how to properly use async and await in this case, but I'm going to expand on exactly why you are getting the result that you see.
txtIDDoc_Detail.Text = ApiData.GetIdDoc().ToString()
Is returning
System.Threading.Tasks.Task`1[System.String]
Because you are calling .ToString on the instance of the Task Task(Of String), not the actual result. Types that don't override .ToString inherit the behavior of Object, which just returns the name of the type as a string.
You probably want this (asynchronous call):
txtIDDoc_Detail.Text = await ApiData.GetIdDoc()
Or this (synchronous call):
txtIDDoc_Detail.Text = ApiData.GetIdDoc().Result
Either of these calls will actually result of the task after it has completed.

Related

How to get return value from function used in thread in vb. Net

Public function myfn1(byval pRequest as string) as string
Dim param(1) object
param(0)=pRequest
Dim T as new thread(Addresof myfn2)
T. Start(param)
End function
Private function myfn2(byval pReq as string) as string
'////some stuff here////
Return lstrResponse
End function
Here myfn1 is accepting requests from user. Sometimes the requests may be concurrent at a time. So I have used thread in myfn1. Myfn2 is actually processing the request and returning the response. So I am willing to get that response in myfn1 after the thread processed the task. What should I do? Or is there any other way out, Pls suggest
You should look into using the Async/Await structure : https://learn.microsoft.com/en-us/dotnet/visual-basic/programming-guide/concepts/async/
For Doing CPU bound work on a separate thread there are a couple options. I like using Task.Run() doc here: https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.run?view=netframework-4.8
You can awaitthe task you create and get the result when it's done like:
SomeVariable = Await Task.Run(Function() FunctionName)

Net.Http.StringContent date format

I'm trying to upload a file with some metadata to a Web API. Everything is fine within the developer environment. But when the same API is hosted in Azure I get the following date parsing error:
Conversion from string "31/03/2019 11:33:52" to type 'Date' is not valid.
I guess StringContent should write the date on ISO 8601 format and it does not.
Next is a simplification of my procedure:
Public Async Function UploadDocFile(oHttpClient as HttpClient, url as string, ByVal oByteArray as Byte(), exs As List(Of Exception)) As Task(Of Boolean)
Dim retval As Boolean
Dim formContent = New Net.Http.MultipartFormDataContent From {
{New Net.Http.StringContent("DateCreated"), now},
{New Net.Http.StreamContent(New IO.MemoryStream(oDocfile.Stream)), "pdf", "pdf.jpg"}
}
Dim response = Await oHttpClient.PostAsync(url, formContent)
If response.StatusCode = 200 Then
retval = True
Else
exs.Add(New Exception(response.ReasonPhrase))
End If
Return retval
End Function
You are using a Collection Initializer to add items to the MultipartFormDataContent; you're expected to follow this overload of the Add method, which accepts two parameters of type HttpContent and String, respectively.
Thus, the second value enclosed in the braces ({}) should be the name of the data content, not its value. On the other hand, the parameter passed to the StringContent constructor should be the value (content), not the name. You basically have them swapped out. If you'd had Option Strict set to On (which is something you should do, BTW), you would've gotten a compiler error and would've easily identified the mistake.
Your code should look something like this:
Dim formContent = New Net.Http.MultipartFormDataContent From
{
{New Net.Http.StringContent(Now.ToString("SomeFormat")), "DateCreated"},
' More values.
}
..where SomeFormat is a format supported by the web API that you're using.

Partitioning lists to execute parallel tasks

I fire tasks to download multiple URLs.
Dim downloadTasksQuery As IEnumerable(Of Task(Of Boolean)) =
From company In companies Select DownloadCompanyFromYahooAsync(company, numberOfDays)
' ***Use ToList to execute the query and start the download tasks.
Dim downloadTasks As IEnumerable(Of Task(Of Boolean)) = downloadTasksQuery.ToList()
Await Task.WhenAll(downloadTasks)
The companies list is kind of containing 2000 URLs. I am observing that URLs added towards the end of the list are more frequently timing out. I have retry logics in place and am handling this timeout situation, which downloads the URL on the next try. However, I dont want to give preferential treatment to a URL just because it appears in the beginning of the list.
Hence was trying to think if we can fork 4 main tasks chunking the URL list into 500 each (probably more manageable) and then use the above code. However, am not able to figure out a way to introduce that without having to rewrite too much in the above code. Any help is greatly appreciated.
EDIT:
Something more like this:
Dim chunkPart As OrderablePartitioner(Of Tuple(Of Integer, Integer)) = Partitioner.Create(1, companies.Count, 500)
Parallel.ForEach(chunkPart, Sub(chunkRange)
For i As Integer = chunkRange.Item1 To chunkRange.Item2 - 1
Dim downloadTasksQuery As IEnumerable(Of Task(Of Boolean)) =
From company In companies.Skip(chunkRange.Item1).Take((chunkRange.Item2 - chunkRange.Item1) + 1) Select DownloadCompanyFromYahooAsync(company, numberOfDays)
Dim downloadTasks As IEnumerable(Of Task(Of Boolean)) = downloadTasksQuery.ToList()
Await Task.WhenAll(downloadTasks)
Next
End Sub
This is with minimal code changes, but the issue is that I cannot use Await inside a Parallel.ForEach.
Any suggestions pls to change this.
Not a VB.NET guy, but I think Stephen Toub's good post on implementing a simple ForEachAsync might be helpful to you.
Some code snippet from his post, it allow you to limit the number of operatons that are able to run in parallel.
public static Task ForEachAsync<T>(this IEnumerable<T> source, int dop, Func<T, Task> body)
{
return Task.WhenAll(
from partition in Partitioner.Create(source).GetPartitions(dop)
select Task.Run(async delegate {
using (partition)
while (partition.MoveNext())
await body(partition.Current);
}));
}
For your specific question, you can then use this as such:
public async Task DownloadForAllCompanies(List<string> companies, int numberOfDays)
{
await companies.ForEachAsync(4, async company =>
{
await DownloadCompanyFromYahooAsync(company, numberOfDays);
});
}

any way to create awaitable EF query?

Is there any way to create an async function that returns an EF query result?
Any way I try to code it VS doesn't like it and says they aren't awaitable.
Private Async Function GetZiptaxByZipcode(ByVal zipcode As String) As Task(Of List(Of ziptax))
Using db
Dim list As List(Of ziptax) = Await (From d In db.ziptaxes
Where d.PostalCode = zipcode
Select d).ToList()
End Using
End Function
Error: System.Collections.Generic.List(of T) is not awaitable
You can't use async/wait this way. await must be applied on a Task, and your ToList() method doesn't return a task.
So you would need to use ToListAsync.
Do note this requires Entity Framework 6. See http://msdn.microsoft.com/en-us/data/jj819165.aspx

.Net 4.5 Await Breakpoints

I couldn't find a full example for PostAsync so I had to piece one together. Therefore, I am not sure if what I am viewing is a limitation with the debugger or I simply did it wrong.
This is what I am trying to do:
I have to go through a list and make a web service call for each item on the list. My thought is that I could use the new 4.5 async stuff to keep it flowing without blocking during each call to the web service.
I've done a tone of research and watched Jon Skeet's video on TekPub, but I'm still not sure if I am doing this correctly. That is, when I set break points my async method never returns control to the caller. It basically seems to go along exactly as my synchronous version.
Question:
Is it normal for the debugger to appear synchronous or does that indicate my code is not implemented correctly?
Here is my Post method:
Public Async Function PostSecureXMLAsync(ByVal username As String, ByVal password As String, ByVal XMLtoSend As String) As Task(Of String)
Dim content = New StringContent(XMLtoSend, Encoding.UTF8, "text/xml")
Dim credentials = New NetworkCredential(username, password)
Dim handler = New HttpClientHandler() With {.Credentials = credentials}
Using client = New HttpClient(handler)
Using response = client.PostAsync(APIurl, content).Result
Return Await response.Content.ReadAsStringAsync()
End Using
End Using
End Function
This is how it is being used:
For Each ListItem In ListObj
...
Result = XMLExchangeObj.PostSecureXMLAsync(Username, Password, Payload).Result
...
Next
I was expecting control to return to the For Each loop while it was waiting for replies from the Web Service, but based on my break points it seems to be running synchronously.
When you're working with Async, you don't want to call Wait or Result. Instead, you should use Await. I see one Result in PostSecureXMLAsync:
Using client = New HttpClient(handler)
Using response = Await client.PostAsync(APIurl, content) ' Changed to Await
Return Await response.Content.ReadAsStringAsync()
End Using
End Using
And there's another one when you call your Async method:
Result = Await XMLExchangeObj.PostSecureXMLAsync(Username, Password, Payload)
This does mean that your calling method must also be Async, which means any methods that call that method should use Await, and must also be Async, etc. This "growth" through your code is perfectly normal. Just allow Async to grow until you reach a natural stopping point (usually an event handler, which you can make Async Sub).