Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UnauthorizedAccessException: Invalid cross-thread access in Silverlight application (XAML/C#)

Tags:

c#

silverlight

Relatively new to C# and wanted to try playing around with some third party web service API's with it.

Here is the XAML code

    <Grid x:Name="ContentGrid" Grid.Row="1">
        <StackPanel>
            <Button Content="Load Data" Click="Button_Click" />
            <TextBlock x:Name="TwitterPost" Text="Here I am"/>
        </StackPanel>
    </Grid>

and here is the C# code

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://api.twitter.com/1/users/show/keykoo.xml");
        request.Method = "GET";

        request.BeginGetResponse(new AsyncCallback(twitterCallback), request);
    }

    private void twitterCallback(IAsyncResult result)
    {
        HttpWebRequest request = (HttpWebRequest)result.AsyncState;
        HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(result);
        TextReader reader = new StreamReader(response.GetResponseStream());
        string strResponse = reader.ReadToEnd();

        Console.WriteLine("I am done here");
        TwitterPost.Text = "hello there";
    }

I'm guessing that this is caused by the fact that the callback executes on a separate thread than the UI? What is the normal flow to deal with these types of interactions in C#?

Thanks.

like image 634
PolandSpring Avatar asked Aug 06 '10 00:08

PolandSpring


3 Answers

A useful way to get easy cross thread access with the CheckAccess call is to wrap up a utility method in a static class - e.g.

public static class UIThread
{
    private static readonly Dispatcher Dispatcher;

    static UIThread()
    {
        // Store a reference to the current Dispatcher once per application
        Dispatcher = Deployment.Current.Dispatcher;
    }

    /// <summary>
    ///   Invokes the given action on the UI thread - if the current thread is the UI thread this will just invoke the action directly on
    ///   the current thread so it can be safely called without the calling method being aware of which thread it is on.
    /// </summary>
    public static void Invoke(Action action)
    {
        if (Dispatcher.CheckAccess())
            action.Invoke();
        else
            Dispatcher.BeginInvoke(action);
    }
}

then you can wrap any calls that update the UI where you may be on a background thread like so:

private void twitterCallback(IAsyncResult result)   
{   
        HttpWebRequest request = (HttpWebRequest)result.AsyncState;   
        HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(result);   
        TextReader reader = new StreamReader(response.GetResponseStream());   
        string strResponse = reader.ReadToEnd();   

        UIThread.Invoke(() => TwitterPost.Text = "hello there");
}   

This way you don't have to know whether you are on a background thread or not and it avoids the overhead of adding methods to every control to check this.

like image 99
Steve Willcock Avatar answered Nov 09 '22 13:11

Steve Willcock


Another way to do this is to use the this.Dispatcher.CheckAccess() function. It is the same as what you get in WPF but it doesn't show up in your intellisense so you may not have seen it. What you do is check if you have access to the UI thread, and if you don't you recursively call yourself back on the UI thread.

private void twitterCallback(IAsyncResult result)
{
    HttpWebRequest request = (HttpWebRequest)result.AsyncState;
    HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(result);
    TextReader reader = new StreamReader(response.GetResponseStream());
    string strResponse = reader.ReadToEnd();

    Console.WriteLine("I am done here");
    ////TwitterPost.Text = "hello there";
    postMyMessage(TwitterPost.Text);
}

private void postMyMessage(string text)
{
    if (this.Dispatcher.CheckAccess())
        TwitterPost.Text = text;
    else
        this.Dispatcher.BeginInvoke(new Action<string>(postMyMessage), text);
}

This is just a very simple example and can get unmanageable once you have more than a few controls. For some WPF/Silverlight stuff i have used a generic function which also takes the UI control to be updated as well as the update itself, that way i don't have one of these functions for every control on the page that may need updating from the results of a background thread.

like image 43
slugster Avatar answered Nov 09 '22 12:11

slugster


As you guessed, silverlight executes all web request asynchronously on a separated thread to avoid freezing the UI.

To dispatch messages to the UI thread you can use the Deployment.Current.Dispatcher.BeginInvoke(delegate, object[]) method.

like image 21
Anero Avatar answered Nov 09 '22 11:11

Anero