Pregunta

Turns out there was an issue with the GetUserByID method, then library was updated and the problem seems to have gone away, still learnt how to better access the GUI off thread.

I wrote an application using the TweetInvi library, It retrieves a users Followers and following, Also their Picture, a link to the picture and twitter ID.

It then iterates through the returned lists and displays them (all in different lists)

Now when I first started with this application I had everything run on the _Click event and ofcourse ir froze the UI until it had completed.

I have now moved the code over to a backgroundworker Thread and It's causing some quirky issues.

Sometimes it will 'choose' not to populate certain lists, other times it will. Sometimes it will load all the lists right except for the Following you list, which filters which of your friends are following you back (with an If statement to filter out Verified accounts)

At first I read that trying to update the UI on the separate thread can cause strange errors, so I have removed any UI control changes except for the Lists it populates.

 private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {   
            BackgroundWorker worker = sender as BackgroundWorker;


        //user name retrieved from text box, rest of this method will pull various bits of data back

        var username = e.Argument.ToString();
        var user = User.GetUserFromScreenName(username);



        Properties.Settings.Default.LastHandle = boxUsername.Text;
        Properties.Settings.Default.Save();



        var usersTweets = user.GetUserTimeline(Convert.ToInt32(txtTweetAmount.Text)).ToList();

        foreach (var userTweet in usersTweets)
        {
            lstSearchTweetList.Invoke((MethodInvoker)delegate
            {
                var searchList = lstSearchTweetList.Items.Add(userTweet.Text);
                  searchList.SubItems.Add(userTweet.CreatedAt.ToString());
            });
        }


        var show = user.GetFollowers(500).ToList();


        foreach (var friend in show)
        {

            string screenName = "@" + friend.ScreenName;
            lstFriend.BeginInvoke((MethodInvoker)delegate
            {
                lstFriend.Items.Add(screenName); // runs on UI thread
            });

        }

        var friends = user.GetFriends(500);
        var followers = user.GetFollowers(500);


        var result2 = followers.Where(follower => friends.All(friend => follower.Name != friend.Name));
        int i2 = 0;
        foreach (var res2 in result2)
        {
            string screenName = "@" + res2.ScreenName;
            lstFollowingChecker.BeginInvoke((MethodInvoker)delegate
                {
                    lstFollowingChecker.Items.Add(screenName);
                });
                i2++;
             //   lblFollowBackAmount.Text = Convert.ToString(i2);
        }
        var result = friends.Where(friend => followers.All(follower => friend.Name != follower.Name));

        //lblFriendCount.Text = "(" + result.Count().ToString() + ")";
        int i1 = 0;
        foreach (var res in result)
        {
                if (res.Verified != true)
                {
                    string screenName = "@" + res.ScreenName;
                    lstFollowerChecker.BeginInvoke((MethodInvoker)delegate
                    {
                        lstFollowerChecker.Items.Add(screenName);
                    });

                    i1++;
                   // lblCheckerCount.Text = Convert.ToString(i1);
                }
        }



        backgroundWorker1.ReportProgress(1,username);
    }

The function calling RunWorkerAsync()

private void btnFind_Click(object sender, EventArgs e)
    {

        //start backgroundworker and clear friends and search lists
        pctProgressBar.Visible = true;

        lstFriend.Items.Clear();
        lstSearchTweetList.Items.Clear();
        lstFollowerChecker.Items.Clear();
        lstFollowingChecker.Items.Clear();
        lstFriend.Items.Clear();
        lstSearchTweetList.Items.Clear();

        if (txtTweetAmount.Text == "")
        {
            txtTweetAmount.Text = "20";
        }

        backgroundWorker1.RunWorkerAsync();

    }

My problem is the strange unexplainable errors are still occurring seemingly randomly.

If this is caused by the lists being updated in the background worker thread, what use is the background worker if I cant use it to do the intensive stuff

I will also include two pictures of a friends account as it better demonstrates the issue's so something handles etc will be blanked out. First Problem is that it sometimes populates a list multiple times, and the "Not following you back list" should be returning @Theilluminati only once Same Screen first Time

Again it returns @Theilluminati but lists it twice. Same Screen Second time

There's also an issue of if I run the below code anywhere, the background worker does not run, that is, It will pull back the picture/name/location but the background worker doesn't run and If I try do it in the actual backgroundworker thread then the lists won't populate.

   var username = boxUsername.Text;
    var user = User.GetUserFromScreenName(username);
    //string ImageURL = user.ProfileImageUrl;
    //string biggerImageURL = ImageURL.Replace("_normal", "");
    //txtImageURL.Text = biggerImageURL;
    //pctDisplaypicture.ImageLocation = biggerImageURL;
    //txtTwitterID.Text = user.Id.ToString();
    //lblFriendCount.Text = "(" + user.FollowersCount + ")";

Any help at all would be appreciated, I'm now struggling to see the use of Backgroundworker if it can't unload work from the UI thread, Sorry for the long post, Thanks for reading.

Fix Attempts

I have disabled the find button while the task is running and the same issue still occurs.

I have also tried using if(working.Cancellationpending == true) to break out of loops once the task has completed once.

I have changed the list foreach loops to the below respectively, and passed the username as a variable instead of pulling it from the control, the problems seem to have just got worse, no lists at all populate now.

lstSearchTweetList.Invoke((MethodInvoker)delegate
                {
                    lstSearchTweetList.Items.Add(userTweet.Text).SubItems.Add(userTweet.CreatedAt.ToString());
                });

 backgroundWorker1.RunWorkerAsync(boxUsername.Text);

var username = e.Argument.ToString();

I have tried both answers as solutions and both still lead to same issue's with differing severity, I am still stuck with the problem that uncommenting the code to retrieve name/picture etc still blocks the backgroundworker from running. No matter where it's run from.

¿Fue útil?

Solución

You may need to use the Invoke method on the list controls that you are trying to update on the background thread like so:

    string  screenName = "@" + friend.ScreenName;
    lstFriend.Invoke((MethodInvoker)delegate {
           lstFriend.Items.Add(screenName); // runs on UI thread
    });

One problem you can have with multi-threading is when you try to access shared resources (Collections, Files, etc.) from multiple threads deadlocking can occur as well as race conditions. In order to do this safely a locking object would be created in this case and lock the code that is accessing the shared resource. This way the resource can only be accessed one at a time.

    //defined globally
    object _MyLockingObject = new object();

and within a certain method locking a list:

    lock(_MyLockingObject)
    {
       myList.Add(item);
    }

Otros consejos

You are breaking a fundamental rule in Windows GUI programming: never access a control from a thread that is not the same thread that created the control. Bad mojo things happen when you break this rule ;)

Pass the username value via backgroundWorker1.RunWorkerAsync(boxUsername.Text);, and read it via e.Arguments as string.

You then need to use BeginInvoke to interact with the UI controls. Ideally, you should optimize this lambda to suspend the control's layout, replace the entire list of items one call, and resume the control's layout.

 // execute on the UI thread
 this.BeginInvoke((Action)(() =>
 {
   lstFriend.Items.Add("@" + friend.ScreenName);
 }), null);

I would use the async Control.BeginInvoke over the sync Control.Invoke option. There does not appear to be a reason to wait on the control to render your change.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top