Problem with BeginInvoke in .NET

2019-07-27 13:25发布

问题:

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:

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


回答2:

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.



回答3:

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.



回答4:

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