0

I'm sure there's some other threads about this, but i think i need a threading for dummies or something.

My problem: I want to fetch a value by a WebRequest and display it. My code looks something like this:

Foo = New Fetcher()
AddHandler Foo.HasResult, AddressOf Me.FetchValue

Private Sub FetchValue()
    If Foo.HasErrors Then
        MyTextBlock.Text = "ERROR"
        Exit Sub
    End IF
    MyTextBlock.Text = Foo.Value 'Here it crashes.....
End sub

Public Class Fetcher
    Public Event HasResult(ByVal F As Fetcher)
    Public WasError As Boolean = True
    Public Value As String = ""

    Public Sub New()
        Dim request As WebRequest = WebRequest.Create("theurl")
        request.BeginGetResponse(New AsyncCallback(AddressOf Me.GetValueAnswer), request)
    End Sub

    Private Sub GetValueAnswer(asynchronousResult As IAsyncResult)
        Dim request As HttpWebRequest = asynchronousResult.AsyncState
        If Not request Is Nothing Then
            Try
                Dim response As WebResponse = request.EndGetResponse(asynchronousResult)
                Using stream As Stream = response.GetResponseStream()
                    Using reader As New StreamReader(stream, System.Text.Encoding.UTF8)
                        Dim responseString = reader.ReadToEnd()
                        Me.Value = ResponseString
                        Me.WasError = False
                    End Using
                End Using
            Catch(Exception Ex)
                Me.WasError = True 'Not needed in this example, i know...
            End Try
        End If
        RaiseEvent HasResult(Me)
    End Sub
End Class

This is a little bit simplified, but it's the same error as well. At the line with the comment "Here it crashes....." i get an exception with "The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))" I can how ever see that my result is fetched when i explore the Foo.

So, how is the right way to do this?

(And yes; if I enter a bad URL or something so that "WasError" is true, of course i get the same exception when i try to set my textblock to "ERROR")

EDIT: After some really strong words, blood sweat and tears, i came up with this change to the FetchValue(), and now it finally works....

If Me.MyTextBlock.Dispatcher.HasThreadAccess Then
    If Foo.HasErrors Then
        MyTextBlock.Text = "ERROR"
        Exit Sub
    End IF
    MyTextBlock.Text = Foo.Value
Else
    Me.MyTestBlock.Dispatcher.RunAsync(Core.CoreDispatcherPriority.Normal, _
        AddressOf Me.FetchValue)
End If

I do how ever get a warning on the row in else that says "Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the Await operator to the result of the call."

Any ideas for how to make this warning go away?

gubbfett
  • 2,157
  • 6
  • 32
  • 54
  • You can't write into a textbox using a different thread than the UI thread. You'll have to use Invoke, check this out http://stackoverflow.com/questions/16529092/update-text-box-properly-when-cross-threading-in-visual-basic-vs-2012-v11 – the_lotus Jul 17 '14 at 12:33
  • Thanks. I tried to do something similar as explained in that thread, but i can't use .InvokeRequired on my textbox. It says it's not a member of that class, and i cant get anything about invoke in the intellisense. – gubbfett Jul 17 '14 at 12:54
  • Is your textbox a System.Windows.Forms.TextBox ? – the_lotus Jul 17 '14 at 13:05
  • Nope, but it's a Windows.UI.Xaml.Controls.TextBox (Actuallt, it's a textBLOCK, something similar to Label... same problems no matter what control i'm using tho) – gubbfett Jul 17 '14 at 13:08
  • I'm not very familiar but seems like you'll have to use .Dispatcher property. http://stackoverflow.com/questions/710034/how-to-read-textbox-text-value-from-another-thread-in-wpf – the_lotus Jul 17 '14 at 13:15
  • Or this http://blog.somecreativity.com/2008/01/10/wpf-equivalent-of-invokerequired/ – the_lotus Jul 17 '14 at 13:16
  • Yea, going nuts here. :) Nothing is as the internet says. I've found that blogpost before I decided to post here. Stuff like Dispatcher.Invoke(), which i see often, is "not a member of Dispatcher" and stuff. Sigh... – gubbfett Jul 17 '14 at 13:48
  • In your event handler, why aren't you accepting the argument? I would expect it to look more like Private Sub FetchValue(Foo as Fetcher) Which would be more thread safe and override the mod-scoped var with the same name. – tgolisch Jul 17 '14 at 14:57
  • It's far, far easier to do this with `HttpClient` and `Async`/`Await`. – Stephen Cleary Jul 18 '14 at 00:08
  • @tgolisch, yea, that was my plan from the start. And still is. The thing is that i have not yet figured out how to send the fetcher values with the AddressOf, but that's absolutley the plan. I thought i'll wait with that and focus on actually catching a value first. :) Good point tho. – gubbfett Jul 18 '14 at 10:17
  • @StephenCleary, can you provide an example of how you mean more specific? I'm all open for suggestions. I'm doing this project for learning so I appreciate all help and suggestions I can have. :) – gubbfett Jul 18 '14 at 10:19

1 Answers1

1

It's far, far easier to do this with HttpClient and Async/Await.

My VB is rusty, but here goes:

Public Class Fetcher
    Public Result As Task(of String)

    Public Sub New()
        Dim client As HttpClient = New HttpClient()
        Result = client.GetStringAsync("theurl")
    End Sub
End Class

Usage:

Foo = New Fetcher()
Try
    Dim data As String = Await Foo.Result
    MyTextBlock.Text = data
Catch(Exception Ex)
    MyTextBlock.Text = "ERROR"
End Try

Note that the Task<T> type handles return values, error conditions, and completion notification. So all the posted code in your Fetcher class is pretty much unnecessary if you use HttpClient with Task<T>. If your Fetcher class doesn't do anything else, then you should probably just remove it entirely:

Dim client As HttpClient = New HttpClient()
Try
    MyTextBlock.Text = Await client.GetStringAsync("theurl")
Catch(Exception Ex)
    MyTextBlock.Text = "ERROR"
End Try
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • This looks really nice! the answer i get from http is json, so I have to parse it and stuff so I think i will keep it in a class - but this looks way better indeed. I'll try this out! :) – gubbfett Jul 19 '14 at 07:47