Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multithreading with XDocument.Load

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>
like image 978
Rhck Avatar asked Feb 11 '23 18:02

Rhck


1 Answers

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
like image 92
Steven Doggart Avatar answered Feb 13 '23 08:02

Steven Doggart