Reputation: 1323
I am transferring a clock control that was running synchronously to an async thread in order to improve performance. I am new to async/await, but am very familiar with the "old" method (Thread/Threadstart/InvokeRequired/Begininvoke)...
I was expecting this to immediately throw an error, and have to add in a callback, etc.. But everything is running without error. My question is, is this ok, or could this potentially encounter threading errors?
I basically just moved the entire function into a new function, and called it with an async call in the original function. Original function was exactly the "GetTimeAsync()" one.
Main Thread property is called "AtomicTime" ... It does await return before hitting the "Dim s as String = """ call - so it does look like it is behaving properly, I am just confused as to why it's not throwing a cross thread error. Is this handled "behind the scenes" now or something?
I looked through numerous threads trying to find an answer - my apologies if I missed one. As you can imagine, search results bring up a multitude of issues spanning numerous actual errors. It's possible this has already been answered, and I simply missed it.
Private Async Sub GetTime()
Await Task.Run(Sub() GetTimeAsync())
Dim s As String = ""
End Sub
Private Sub GetTimeAsync()
Try
Dim ntpServer As String = "wwv.nist.gov"
Dim ntpData(47) As Byte
ntpData(0) = &H1B
Dim serverReplayTime As Byte = Convert.ToByte(40)
Dim addresses = Dns.GetHostEntry(ntpServer).AddressList
Dim EndP As IPEndPoint = New IPEndPoint(addresses(0), 123)
Dim soc As Socket = New Socket(AddressFamily.InterNetwork,
SocketType.Dgram, ProtocolType.Udp)
soc.ReceiveTimeout = 3000
soc.Connect(EndP)
soc.Send(ntpData)
soc.Receive(ntpData)
soc.Close()
soc.Dispose()
Dim intPart As UInt32 = BitConverter.ToUInt32(ntpData, serverReplayTime)
Dim fractPart As UInt32 = BitConverter.ToUInt32(ntpData, serverReplayTime + 4)
intPart = SwapEndianness(intPart)
fractPart = SwapEndianness(fractPart)
Dim mills As Object = (intPart * 1000) + ((fractPart * 1000) / &H100000000L)
Dim networkDateTime As Object = (New DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc)).AddMilliseconds(mills)
Dim localTime As DateTime = networkDateTime.ToLocalTime()
AtomicTime = localTime '***HERE*** This works... Should it????
addresses = Nothing
EndP = Nothing
networkDateTime = Nothing
Catch ex As Exception
Dim s As String = ex.ToString()
End Try
End Sub
Upvotes: 0
Views: 147
Reputation: 1323
I went with Mark's suggestion from main thread comments - using provided socket async functions. This took a little more research, so I'm going to post the code (also, comments welcome.. This is completely from code examples I found online, but it is working - so I'm calling this one resolved, even though it's not technically using async/await functions/operators, it is using the built ins, which is probably preferable when available.
First, we need a connect, send, and receive event to hook up to for callbacks - these are going to be form-level variables.
Public Class YourClass
Private ConnectDoneEvent As System.Threading.ManualResetEvent
Private SendDoneEvent As System.Threading.ManualResetEvent
Private ReceiveDoneEvent As System.Threading.ManualResetEvent
'...
End Class
Don't forget to initialize to avoid null reference exceptions. They take a boolean on initialization as to whether the block is set or not. You probably want it unset/false, but verify that so you don't block a thread immediately on program start.
Next, we need to initiate the connect, wait on it, once it comes back, initiate the send, wait...receive. I felt this was one of the coolest parts about this experience - this is super simple... We have 3 functions to make, for callbacks on each event (connect, send, receive). They are pretty much identical:
Private Sub ConnectCallback(ar As IAsyncResult)
Try
Dim soc As Socket = CType(ar.AsyncState, Socket)
soc.EndConnect(ar)
ConnectDoneEvent.Set()
Catch ex As Exception
End Try
End Sub
Private Sub SendCallback(ar As IAsyncResult)
Try
Dim soc As Socket = CType(ar.AsyncState, Socket)
Dim iBytesSent As Integer = soc.EndSend(ar)
SendDoneEvent.Set()
Catch ex As Exception
End Try
End Sub
Private Sub ReceiveCallback(ar As IAsyncResult)
Try
Dim soc As Socket = CType(ar.AsyncState, Socket)
Dim iBytesRcvd As Integer = soc.EndReceive(ar)
ReceiveDoneEvent.Set()
Catch ex As Exception
End Try
End Sub
Now, all we need to do is make the calls. This is pretty straightforward
Dim ntpServer As String = "wwv.nist.gov"
Dim ntpData(47) As Byte
ntpData(0) = &H1B
Dim serverReplayTime As Byte = Convert.ToByte(40)
Dim addresses = Dns.GetHostEntry(ntpServer).AddressList
Dim EndP As IPEndPoint = New IPEndPoint(addresses(0), 123)
Dim soc As Socket = New Socket(AddressFamily.InterNetwork,
SocketType.Dgram, ProtocolType.Udp)
soc.ReceiveTimeout = 3000
soc.BeginConnect(EndP, AddressOf ConnectCallback, soc)
ConnectDoneEvent.WaitOne()
soc.BeginSend(ntpData, 0, ntpData.Length, SocketFlags.None, AddressOf SendCallback, soc)
SendDoneEvent.WaitOne()
soc.BeginReceive(ntpData, 0, ntpData.Length, SocketFlags.None, AddressOf ReceiveCallback, soc)
ReceiveDoneEvent.WaitOne()
soc.Close()
soc.Dispose()
Error handling excluded. Make sure to include it in production versions.
Upvotes: 0
Reputation: 456322
Properties do not have a thread affiliation. So a "main thread property" doesn't make sense.
What you're probably thinking of is the cross-thread InvalidOperationException
that happens when accessing UI object instances from a non-UI thread. Since in this case, AtomicTime
is not a UI object, it can't have UI affinity. Now, if you were trying to update a label or something, then that would have UI affinity and you would get the exception.
The ideal way to do this asynchronously is to use a truly asynchronous API, not Task.Run
. Task.Run
is fine (for UI apps only) if you have CPU-bound work to do or only have a synchronous API available, but it's more of a fallback, use-it-if-you-have-to kind of solution. Pure asynchronous code is better.
I have an async
intro on my blog that should help you get started.
Upvotes: 2