I am trying to make my code work in a separate thread but can't make it work. I have tried following several different multi-threading examples from the internet using delegates, but nothing fixed my problem.
I need to load data from an XML file via a URL and then show some data from the XML in labels. Loading the XML sometimes takes a long time and my application is not responding while the loading is in process. I don't know what else I should try.
Here's an example that works to load the XML without multi-threading (makes the UI non-responsive):
Dim xmlRoot1 As XElement = XDocument.Load("http://example.com/api/books.xml").Root
Label1.Text = xmlRoot1.<bookstore>.<book>(0).<title>.Value
Label2.Text = xmlRoot1.<bookstore>.<book>(1).<title>.Value
' ...
And here is an example of the XML that I am loading:
<xml>
<bookstore>
<book>
<title>Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price>30.00</price>
</book>
<book>
<title>Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
<book>
<title>XQuery Kick Start</title>
<author>James McGovern</author>
<year>2003</year>
<price>49.99</price>
</book>
<book>
<title>Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price>39.95</price>
</book>
</bookstore>
</xml>
If you are using Visual Studio 2012 and the 4.5 framework, or higher, you have access to the Async
and Await
keywords which make things like this much easier. However, you can only use the Await
keyword on a Task
object. Since XDocument
regrettably does not provide a convenient LoadAsync
method that returns a Task
object, it's more difficult to use it with the Task-Based Asynchronous Pattern (TAP). The simplest way is to create a method like this:
Public Async Function LoadXDocumentAsync(uri As String) As Task(Of XDocument)
Dim t As New Task(Of XDocument)(Function() XDocument.Load(uri))
t.Start()
Return Await t
End Function
Then you can call it like this:
Dim doc As XDocument = Await LoadXDocumentAsync("http://example.com/api/books.xml")
Label1.Text = doc.Root.<bookstore>.<book>(0).<title>.Value
Label2.Text = doc.Root.<bookstore>.<book>(1).<title>.Value
However, by calling the Start
method on the Task
object, like that, it likely starts a new thread to accomplish the work. If you are worried that it will take a long time to parse the XML, that's the best thing to do anyway, but if you are only concerned about the download-time, it's technically inefficient to start a separate thread just to have it sit there idle while the XML is downloaded from the URI. So, while it's a little bit more complicated, if you are only concerned about the download being performed asynchronously, it's technically more efficient to do something like this:
Public Async Function LoadXDocumentAsync(uri As String) As Task(Of XDocument)
Dim client As New WebClient()
Return XDocument.Parse(Await client.DownloadStringTaskAsync(uri))
End Function
You can then call it in the same way as the first example. This second example takes advantage of the fact that the WebClient
class does provide a task-based Async
method that we can use. Therefore, even though XDocument
doesn't provide a task-based Async
method, we can still at least download the XML using the task-based WebClient
method and then, once we get it, just parse the XML string with the XDocument
object back on the calling thread.
Presumably, Microsoft will be adding a LoadAsync
method to the XDocument
class in some future version of the framework, but until then you have to make the Async
function yourself, like I did in the above examples.
If you are not able to utilize TAP, I would suggest using the BackgroundWorker
component. For instance:
Public Class Form1
Private _doc As XDocument
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
_doc = XDocument.Load("http://example.com/api/books.xml")
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
Label1.Text = _doc.Root.<bookstore>.<book>(0).<title>.Value
Label2.Text = _doc.Root.<bookstore>.<book>(1).<title>.Value
End Sub
End Class
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With