1

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.

Pheonix2105
  • 1,001
  • 2
  • 9
  • 24
  • Where do you call `backgroundWorker1.RunWorkerAsync()` ? Could be that your are creating more than one instance of the backgroundWorker? – etaiso Mar 16 '14 at 15:30
  • Hi Sorry I'll update the post, but it's simple called from the "Find @User" click event, my mouse is slightly faulty in that it will double click sometimes but I have doubly checked its not the cause as I'm aware of it almost every time I go to click. – Pheonix2105 Mar 16 '14 at 15:33
  • Thanks for the edit, I sometimes get a bit overwhelmed when posting to StackOverflow. – Pheonix2105 Mar 16 '14 at 15:37
  • You should disable button `btnFind` while your async task is running, otherwise user will be able to "run" (try to) it again which will cause an exception. So first I would implement this behavior (disable when running, enable when done - you can do that in `RunWorkerComplete` event). – etaiso Mar 16 '14 at 15:40
  • Hi Etaiso, I'll do it now and report back :) update - Still got the same issue. – Pheonix2105 Mar 16 '14 at 15:45

2 Answers2

4

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);
    }
The Mahahaj
  • 696
  • 6
  • 8
  • Hello Vishwaram, thank you for taking the time to answer, I have tried what you have suggested with invoking but I'm not sure I understand what you mean with the Object locking, do you mean instantiating the User object once and locking it everytime a function queries it? If so I'd need to supply with a name from the textbox holding the username, you cannot do this in global scope can you? – Pheonix2105 Mar 16 '14 at 16:32
  • @Pheonix2105, the second statement was more of a general multi-threading comment in case you wanted multiple threads to access the same shared resource. It may not have any bearing on what you are trying to accomplish. – The Mahahaj Mar 16 '14 at 18:11
0

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.

Steve Jansen
  • 9,398
  • 2
  • 29
  • 34
  • HI Steve, Thanks for the answer, I'll try it shortly and report back, I'm just trying to understand what I'm changing before I get to the point of not understanding my code anymore haha. – Pheonix2105 Mar 16 '14 at 16:20