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.
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; });
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.
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.
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 });