locked
UI thread blocks RRS feed

  • Question

  • Hi,

    Since beta 2 and the changing of the web client to work on a background thread made me a problem which i would like to advise myself before doing anything stupid without efforts since it maybe is a know issue.

    I have a LoadData in each custom user control that corresponds to a section of the website. Each section loads data independetly from other. In that method i invoke a call to a helper class to exchange data with the server. That class uses WebRequest to process the comunication.

    I have an animation which acts as loader, its a logo rotating. Since beta 2, while loading i have blocks of the animation as well as every part of the UI for some seconds. 

    I suspect that i should be now throwing LoadData in another thread, but i would like to hear an opinion about this.

    Thx,

    Best regards,

    Nuno Santos

    Friday, June 27, 2008 5:35 PM

Answers

  • Thank you for posting the code that you are using.

    To answer your question - LINQ *can* use deferred query plans, if the underlying LINQ Provider supports it. LINQ to XML does support it. The catch is that different LINQ providers can be chained together, and as soon as it hits a query expression that requires immediate execution (instead of deferred), the query is executed right away. Any query expression that needs the full result set or that creates a copy of the results qualifies as an "immediate" expression. ToList() and ToArray() as well as OrderBy() and others fall into this category.

    From looking at this code, the LoadCompleted() method is indeed being executed on a background threadpool thread already, so the placement of the calls to BeginInvoke() is correct. I am assuming that the "Data" property of "background" is some custom property that is lightweight and doesnt affect the UI directly. The other calls that assign the DataContext are properly executed on the UI thread, and since that assignment has the potential to change the UI visual tree, it must remain in the call to BeginInvoke(). You can't really break that down much more.

    The only other optimization that could probably be made is that you could consolidate those three calls to Dispatcher.BeginInvoke() into a single call. This would be a very minor optimization though, and I doubt it would even produce a noticeable improvement.

    Fixing the deferred LINQ queries will most likely provide the greatest benefit and might be all you need to do.

    Monday, June 30, 2008 10:00 AM

All replies

  • It was strange te way the UI thread was modified for Beta 2, without giving us the capability to do "Thread Priority", or providing a "Yield" method, but it results in the problem you hav described.

    I've posted a lot about this, but no one wants to address it, and it's just a matter of time before those that don't pay attention, will run into it to their dismay.

    However, your animation and your UI gets blocked because when the background process finishes(it doesn't block when you're on the background thread), it takes FULL control over the UI thread, and all other activity will stop until it finishes.  Animations will stop, no clicks in the UI will visually show, etc.

    This is way more than odd, since so much trouble was gone through to FORCE async operation on background threads, only to have no ability to thread the user interface, where the REAL client does business.

    Anyway, if you want to read more about this, take a look at the link below.

    Getting back to the UI Thread in Silverlight 2 Beta 2

    Sam...

     

    Friday, June 27, 2008 7:22 PM
  • Sinosoudal, please post some sample code and perhaps someone will be able to fully assist you.

    Saturday, June 28, 2008 3:45 PM
  • Ok,

    Look at the following:

     

    public void Initialize()
    {
      LoadData()
    }

    private void LoadData()

    {

    communicater =
    new Communicater(new Uri(Page.ServiceUrl), "POST",

    new KeyValuePair<string, string>("operation", "retrieve"),

    new KeyValuePair<string, string>("section", "home"));communicater.ResponseComplete += new HttpResponseCompleteEventHandler(LoadCompleted);

    communicater.Execute();

    }

    Where communicater is a helper class for making a request.

    The response is handled in LoadCompleted:

    private void LoadCompleted(HttpResponseCompleteEventArgs e)

    {

    if (e.Response != null)

    {

    try

    {

    // obtaining the data from the remote server

    XDocument xmlHome = XDocument.Load(XmlReader.Create(new StringReader(e.Response))); photos = from ph in xmlHome.Descendants("background").Descendants("photo")

    select new Photo

    {

    Id = (
    string)ph.Attribute("Id"),Uri = (string)ph.Attribute("Uri")

    };

    background.Data = photos;

    Dispatcher.BeginInvoke(
    delegate()

    {

    // if there are any photos

    if (photos.Count<Photo>() > 0)

    {

    backgroundImagesList.DataContext = photos;

    }

    else

    {

    backgroundImagesList.DataContext =
    null;

    }

    });

    // getting expositions data

    expositions = from item in xmlHome.Descendants("expositions").Descendants("exposition")

    select new Exposition

    {

    Id = (
    string)item.Attribute("Id"),

    Title = (string)item.Attribute("Title"),

    Author = (string)item.Attribute("Author"),

    DescriptionPt = (string)item.Attribute("Description_Pt"),

    DescriptionEn = (string)item.Attribute("Description_En"), Photos = from ph in item.Descendants("photo")

    select new Photo

    {

    Id = (
    string)ph.Attribute("Id"),Uri = (string)ph.Attribute("Uri")

    },

    Start = DateTime.Parse((string)item.Attribute("Start")),

    End = DateTime.Parse((string)item.Attribute("End")),

    };

    Dispatcher.BeginInvoke(
    delegate()

    {

    if (expositions.Count<Exposition>() > 0)

    {

    expositionsContainer.DataContext = expositions.Last();

    expositionsContainer.Visibility =
    Visibility.Visible;

    }

    else

    expositionsContainer.Visibility = Visibility.Collapsed;

    });

     

    events =
    from item in xmlHome.Descendants("events").Descendants("event")

    select new Event

    {

    Id = (
    string)item.Attribute("Id"),

    Title = (string)item.Attribute("Title"),

    DescriptionPt = (string)item.Attribute("Description_Pt"),

    DescriptionEn = (string)item.Attribute("Description_En"),

    Photos = from ph in item.Descendants("photo")

    select new Photo

    {

    Id = (
    string)ph.Attribute("Id"),Uri = (string)ph.Attribute("Uri")

    },

    Start =
    DateTime.Parse((string) item.Attribute("Start"))

    };

    Dispatcher.BeginInvoke(
    delegate()

    {

    if (events.Count<Event>() > 0)

    {

    eventsContainer.DataContext = events.Last();

    eventsContainer.Visibility =
    Visibility.Visible;

    }

    else

    eventsContainer.Visibility = Visibility.Collapsed;showBoxes.BeginTime = new TimeSpan(0, 0, 5);

    showBoxes.Begin();

    });

    }

    catch (Exception exc)

    {

    Logger.Log(exc);

    }

    }

    }

    And that's it. If i comment LoadData there is no animation block. So I believe this is related with the response handling.

    Any tips?

    Thx,

    Nuno

    Saturday, June 28, 2008 5:37 PM
  • Sinosoudal, please post some sample code and perhaps someone will be able to fully assist you.

    Unless you're Microsoft, and can change the threading model, you won't be able to help him.

    This is a fundamental problem with Beta2.

    Sam...

     

    Saturday, June 28, 2008 8:24 PM
  • Sinosoidal, can you give some details about what the "Communicater" class looks like? Depending upon the implementation there, the entire LoadCompleted method might already be getting executed on the main UI thread, which would make the calls to Dispatcher.BeginInvoke() pointless, but more importantly it would mean that all the other code in that method was getting executed on the main thread - even the stuff that has nothing to do with UI updates.

    First thing I can see from this code is that I think you have some hidden costs due to deferred LINQ. You are constructing three LINQ queries - "photos", "expositions", and "events". Because of deferred execution (normally good for performance and memory) your three queries are actually getting executed seven times:

    background.Data = photos; <-- this forces the query to enumerate
    if (photos.Count<Photo>() > 0) <-- this forces the same query to enumerate again
    backgroundImagesList.DataContext = photos; <-- third enumeration of this same query


    if (expositions.Count<Exposition>() > 0) <-- forces an enumeration
    expositionsContainer.DataContext = expositions.Last(); <-- another enumeration of the same query

    if (events.Count<Event>() > 0) <-- you guessed it, forces an enumeration
    eventsContainer.DataContext = events.Last(); <-- another enumeration for this query

    As a first step, you can probably improve this by simply forcing enumeration once for each query, which would reduce the workload by over 50%. The easiest way to do that is to call the ToArray() or ToList() extension methods on those queries right after creating them. For example:

    photos = photos.ToArray();

    Do that right after defining the three queries and see if that provides any benefit. It probably won't eliminate the pauses but it might make them more bearable.

    This kind of thing shows up a lot in code that relies on LINQ to SQL, but I have also seen it impact LINQ to XML fairly often too.

     

    If you can share information about how the Communicater class is defined, then perhaps there are further gains that can be made based on that.

    Sunday, June 29, 2008 8:04 PM
  •  

    Hi Keith, Thx for the reply. That happens because of the nature of LINQ, right? LINQ processes the query every time, even after being in variable, like photos. I wasn't paying attention to that, mostly because i don't hvae that sensibility yet. I have bought a LINQ book but i'm still in the beggining, but now that you refer that i think i'm remembering something.

     About the communicater, this is a class that someone in this forum shared. If you can tell me as well a way of getting the real progress of this kind of communications that would be wonderfull. Thx.namespace PRW

    {

    public class Communicater

    {

    #region properties

    private HttpWebRequest Request { get; set; }

    public Dictionary<string, string> PostValues { get; private set; }

    #endregion

    #region
    events

     

    public event HttpResponseCompleteEventHandler ResponseComplete;private void OnResponseComplete(HttpResponseCompleteEventArgs e)

    {

    if (this.ResponseComplete != null)

    {

    this.ResponseComplete(e);

    }

    }

    public static event HttpResponseFailedEventHandler ResponseFailed;private static void OnResponseFailed(HttpResponseFailedEventArgs e)

    {

    if (Communicater.ResponseFailed != null)

    {

    Communicater.ResponseFailed(e);

    }

    }

    #endregion

    public Communicater(Uri requestUri, string method, params KeyValuePair<string, string>[] postValues)

    {

    this.Request = (HttpWebRequest)WebRequest.Create(requestUri);

    this.Request.ContentType = "application/x-www-form-urlencoded";

    this.Request.Method = method;this.PostValues = new Dictionary<string, string>();

     

    if (postValues != null && postValues.Length > 0)

    {

    foreach (var item in postValues)

    {

    this.PostValues.Add(item.Key, item.Value);

    }

    }

    }

    public void Execute()

    {

    try

    {

    this.Request.BeginGetRequestStream(new AsyncCallback(Communicater.BeginRequest), this);

    }

    catch (Exception e)

    {

    System.Diagnostics.
    Debugger.Log(0, "debug", "failed to execute: " + e.Message);

    }

    }

    private static void BeginRequest(IAsyncResult ar)

    {

    try

    {

    Communicater helper = ar.AsyncState as Communicater;if (helper != null)

    {

    if (helper.PostValues.Count > 0)

    {

    using (StreamWriter writer = new StreamWriter(helper.Request.EndGetRequestStream(ar)))

    {

    foreach (var item in helper.PostValues)

    {

    writer.Write(
    "{0}={1}&", item.Key, item.Value);

    }

    }

    }

    helper.Request.BeginGetResponse(
    new AsyncCallback(Communicater.BeginResponse), helper);

    }

    }

    catch (Exception e)

    {

    System.Diagnostics.
    Debugger.Log(0, "debug", "failed to begin request: " + e.Message);

    }

    }

    private static void BeginResponse(IAsyncResult ar)

    {

    Communicater helper = ar.AsyncState as Communicater;

     

    if (helper != null)

    {

    HttpWebResponse response = (HttpWebResponse)helper.Request.EndGetResponse(ar);

     

    if (response != null)

    {

    Stream stream = response.GetResponseStream();if (stream != null)

    {

    using (StreamReader reader = new StreamReader(stream, System.Text.Encoding.UTF8))

    {

    helper.OnResponseComplete(
    new HttpResponseCompleteEventArgs(reader.ReadToEnd()));

    }

    }

    else

    {

    OnResponseFailed(
    new HttpResponseFailedEventArgs());

    }

    }

    else

    {

    OnResponseFailed(
    new HttpResponseFailedEventArgs());

    }

    }

    else

    {

    OnResponseFailed(
    new HttpResponseFailedEventArgs());

    }

    }

    }

    public delegate void HttpResponseCompleteEventHandler(HttpResponseCompleteEventArgs e);

     

    public class HttpResponseCompleteEventArgs : EventArgs

    {

    public string Response { get; set; }public HttpResponseCompleteEventArgs(string response)

    {

    this.Response = response;

    }

    }

     

    public delegate void HttpResponseFailedEventHandler(HttpResponseFailedEventArgs e);

    public class HttpResponseFailedEventArgs : EventArgs

    {

    public HttpResponseFailedEventArgs()

    {

    }

    }

    }

    Monday, June 30, 2008 4:24 AM
  • Thank you for posting the code that you are using.

    To answer your question - LINQ *can* use deferred query plans, if the underlying LINQ Provider supports it. LINQ to XML does support it. The catch is that different LINQ providers can be chained together, and as soon as it hits a query expression that requires immediate execution (instead of deferred), the query is executed right away. Any query expression that needs the full result set or that creates a copy of the results qualifies as an "immediate" expression. ToList() and ToArray() as well as OrderBy() and others fall into this category.

    From looking at this code, the LoadCompleted() method is indeed being executed on a background threadpool thread already, so the placement of the calls to BeginInvoke() is correct. I am assuming that the "Data" property of "background" is some custom property that is lightweight and doesnt affect the UI directly. The other calls that assign the DataContext are properly executed on the UI thread, and since that assignment has the potential to change the UI visual tree, it must remain in the call to BeginInvoke(). You can't really break that down much more.

    The only other optimization that could probably be made is that you could consolidate those three calls to Dispatcher.BeginInvoke() into a single call. This would be a very minor optimization though, and I doubt it would even produce a noticeable improvement.

    Fixing the deferred LINQ queries will most likely provide the greatest benefit and might be all you need to do.

    Monday, June 30, 2008 10:00 AM
  • Ok. Thank for your analysis and reply.

    I'm a little frustated because this kind of hangs aren't a good thing... 

    But thanks anyway.

    Nuno

    Friday, July 4, 2008 8:12 AM