Link to home
Start Free TrialLog in
Avatar of dogsdieinhotcars
dogsdieinhotcars

asked on

C# 2.0 Threading Question

I have a probably common threading issue, but can't get my head around how to easily deal with it.  

On a winform, I have a grid and on the bottom of the form I have detail information about the current item on the grid that is populated from a web service.  The web service is pretty slow, so if a user is just scrolling up and down in the grid, I don't want them to be slowed by waiting for the slow web service that populates the bottom part of the form.  Hence I thought, this is a good case for a thread.

So I tried the background worker which wasn't appropriate because I couldn't "kill" it off --- so if a user scrolled up and the background thread from the prior item hadn't finished - an error is thrown.

Now I've tried good old standard thread.  I seem to not be doing it right because I get an error for updating the UI from a different thread than it was created on.  However, there should not be any deadlock situation ever because I'd like the thread to die immediately when the user scrolls to the next record --- i.e. there should only be one background thread ever at once and if it hasn't finished by the time the user goes to the next record, I'd like to destroy it and start it again.

How can I go about this?  Any psuedo/basic code would be helpful.

Main Thread:
  (Grid Position Changed)
      Is background thread active?  
          kill background thread.
     Activate Background Thread to populate bottom of form.


Background Thread:
   Call Slow web service.
   Populate bottom of form.


That's my psuedo code, what should I be doing to make that happen in C#?

ASKER CERTIFIED SOLUTION
Avatar of Mike Tomlinson
Mike Tomlinson
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of dogsdieinhotcars
dogsdieinhotcars

ASKER

Ok, I think I've about got it...  I guess what I'm not getting (this is slow getting through my thick skull - sorry!!) is how do I dump the background thread -immediately - and start over when the user goes to the next record in the grid (or if I should be going with another plan).  So, when the user hits a record on a grid, and the background thread may take three seconds to pull down the info for the detail portion --- but if the user has gone to another record on the grid, then I'd want to immediately kill that background thread and start a new one to get the new detail info for the current record...does that make sense or am I talking in gibberish??  

What you're saying makes perfect sense...

The ability to cancel the pending thread is really dependent upon what you are doing inside of it.  If the work being done is a long "blocking call" (the code stops at a particular line until data is returned) then you can't really cancel it.  But if the work being done in the thread can be monitored by a polling loop, then you can toggle a flag and exit the loop.

Consider this "simulated" example.  Create a new project with a ListBox, a Label and a BackgroundWorker component.

        private void Form1_Load(object sender, EventArgs e)
        {
            int i;
            for (i = 1; i < 11; i++)
                listBox1.Items.Add("Item" + i);
        }

        private int index;
        private bool cancelled;
        private string data;

        private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (backgroundWorker1.IsBusy)
            {
                cancelled = true;
                while (backgroundWorker1.IsBusy)
                {
                    System.Threading.Thread.Sleep(100);
                    Application.DoEvents();
                }
            }
            index = listBox1.SelectedIndex;
            cancelled = false;
            backgroundWorker1.RunWorkerAsync();
        }

        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            // A simulated long process...
            // The length of time to complete is directly proportional
            // to the index of the item selected in the ListBox

            // possibly make an asynchronous call here
            // and toggle a flag when it is complete
            // monitor that flag and the cancelled flag below
            // in the polling loop

            DateTime dt = DateTime.Now.AddSeconds(index + 1);            
            do
                System.Threading.Thread.Sleep(100);
            while (!cancelled & ((TimeSpan)dt.Subtract(DateTime.Now)).TotalMilliseconds > 0);

            if (!cancelled)
            {
                data = "Item" + (index + 1) + " data update.";
            }
           
        }

        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (!cancelled)
            {
                label1.Text = data;
            }          
        }
In case someone here happends to mention it, I will tell you ahead of time DO NOT use Thread.Abort();  There really is not a safe way to tell you thread so quit.  How are resources to be cleaned up?  What you need to do is architecturally design a solution to resolve this.  First the idea of calling a web service for each record is not a good idea.  If network traffic is slow or the server is on high load the user experience is going to be pretty bad regardless of whether your using threads or not.  Without putting to much though into the best solution I think the following would be much better.  Instead of calling a web service to retrieve detailed information for each record you should call a web service to retrieve detail information for a large number of the records.  So when the app starts it goes and retrieves detail information for the first 100 records.  The web service would not be called again until the user gets to the next 100.

Depending on how your getting the detail information (database speed) I would almost guarantee that it takes almost the same amount of time to return hundreds of detail records as it does to return 1.  These numbers of course are made up.  You might so no difference in getting 1000 records.  It depends on a lot of things.  Either way I think youll find this to work much better.  If anything it takes care of your worry about the user going to the next record and having to stop the last ws call.  The chances of the user going to the next 100th, 300th, or 500th record before the previous web service call is pretty unlikely and if it does happen let it continue and still populate the old records in case they decide to scroll back up.

good luck,
gary paulish
Thanks for the help and advice.  It's going to take me a day or two to process/understand this (again, I think I was dropped on the head as a child or something), but I decided to award points before I've got it down so I don't make you guys wait while I figure it out  Thanks again!!