views:

261

answers:

4

I have the following code to send a query to youtube and send the total result to a textbox. If I just alert the result, it's OK but I can't assign the result to a textbox. Please explain to me why?

private void SearchVideo(string keyword)
{
    string orderBy = "";
    switch (cboSortBy.SelectedIndex)
    {
        case 1: orderBy = "published"; break;
        case 2: orderBy = "viewCount"; break;
        case 3: orderBy = "rating"; break;
        default: orderBy = "relevance"; break;
    }
    SearchDelegate sd = Search;
    sd.BeginInvoke(keyword, orderBy, SearchCompleted, sd);
}

private void SearchCompleted(IAsyncResult ar)
{
    if (null == ar) return;
    SearchDelegate sd = ar.AsyncState as SearchDelegate;
    Feed<Video> result = sd.EndInvoke(ar);
    txtSearch.Text = result.TotalResults.ToString();
}

private Feed<Video> Search(string keyword, string orderBy)
{
    YouTubeQuery query = new YouTubeQuery(YouTubeQuery.DefaultVideoUri);
    query.OrderBy = orderBy;
    query.Query = keyword;
    query.SafeSearch = YouTubeQuery.SafeSearchValues.None;
    return GetRequest().Get<Video>(query);
}

And the error

Cross-thread operation not valid: Control 'txtSearch' accessed from a thread other than the thread it was created on.

+1  A: 

You're calling BeginInvoke so your delegate is being invoked on a thread-pool thread. You can't access the UI from that thread-pool thread; you need to call Invoke or BeginInvoke on the control to then use the results on the UI thread. For instance, using an anonymous method:

txtSearch.BeginInvoke((MethodInvoker) delegate() 
    { txtSearch.Text = result.TotalResults.ToString(); }
);

Or using a lambda expression, and with a separate local variable just for clarity:

MethodInvoker action= () => { txtSearch.Text = result.TotalResults.ToString();};
txtSearch.BeginInvoke(action);

Using Invoke will make the calling thread block until the UI thread has invoked the delegate; BeginInvoke is non-blocking.

EDIT: If the problem is that result.TotalResults is the bit that takes a long time, do that bit still on the background thread:

string text = result.TotalResults.ToString();
txtSearch.BeginInvoke((MethodInvoker) delegate() { txtSearch.Text = text; });
Jon Skeet
BeginInvoke still block the GUI :(
A New Chicken
@A New Chicken: What exactly do you mean? BeginInvoke is non-blocking both in terms of calling it on a delegate and on a control. Using BeginInvoke in the way I've described *will* work, and without blocking the UI. If you're having problems, please give a short but complete program demonstrating the issue.
Jon Skeet
Here is my project in C# 2008EE http://www.mediafire.com/?n220ddoi2i2Please check it for me, thanks you.
A New Chicken
Thanks you very much for your suggestion :D
A New Chicken
Your link doesn't work...
Jon Skeet
A: 

Because access to Forms controls isn't inherently thread-safe, the debugger is warning you that you're breaking the rules by accessing it from a different thread. Instead, you can Invoke the control directly to get the results you want. There's a great, comprehensive Microsoft tutorial of how to do this here.

John Feminella
Using the thread-safe method take longer time than the normal :(
A New Chicken
A: 

The error message is telling you exactly what the problem is. You can not safely manipulate UI controls on a thread different than the one that created the control; the debugger is designed to catch this (see MSDN for details).

So, you either need to call BeginInvoke on the control so that it is executed on the UI thread, or you need to set up some mechanism of communication between the invoked thread and the UI thread. Obviously the former can be accomplished simply with TextBox.BeginInvoke:

txtSearch.BeginInvoke(sd, new object[] { keyword, orderBy, SearchCompleted });
Jason
+1  A: 

Instead of Delegate.BeginInvoke you might consider using a BackgroundWorker. A BackgroundWorker raises the RunWorkerCompleted event after it has finished, which runs in the UI thread, so you can update your user interface there.

Heinzi
I've already tried the BackgroundWorker but it's still block the UI :(
A New Chicken
That's strange; I've successfully used BackgroundWorker in many projects where they *don't* block the UI. Are you sure you used it correctly? In your event handler: Just start the BackgroundWorker (nothing else). In BW.DoWork: Do the work. In BW.RunWorkerCompleted: Update the UI.
Heinzi
Is it possible that accessing property `TotalResults` actually performs all the work? In that case, you should move accessing that property to the background thread and just pass the resulting string to the UI thread.
Heinzi
I don't think so. The only method that doesn't affect the responsiveness of the UI is the one I post, but it's a non thread-safe and the Microsoft method: http://msdn.microsoft.com/en-us/library/ms171728%28VS.80%29.aspx still didn't solve the problem >.>
A New Chicken
Can you try moving TotalResults in the background tread like Jon suggested? It's really the only sensible explanation as to why your UI thread blocks...
Heinzi
Thanks you very much for your suggestion :D
A New Chicken