views:

511

answers:

4

I have a scenario. I have a list of user id's displayed on the windows form. As soon as I click one of the user id's I go and get the user details from the DB. To retain the responsiveness of the application, on selection changed event of the listbox, I create new BackgroundWorker(BW) objects and hit the DB. I show 'Searching user 'abc'...' in the status bar.

Now, if the user moves between the user ids using arrow keys (4-5 times), by above design I have created multiple BW objects to make the request. But finally when the data comes back for particular user (which may not be the one where user is currently selected in the listview), since it was async call, I still end up displaying all the users in the status bar.

What I would like to do is, I want to go and get the details only for the last user. Till that time I want to display 'Searching user...' only.

Please let me know solution for this...

+2  A: 

When the user switches users, you can cancel the worker processes that are currently running (check to make sure they are running). I believe that would accomplish what you are after.

Cody C
Yep, something like... if (worker != null
Steve Wortham
A: 

One option would be to keep a string field in the form containing the user ID. In the background worker, after you hit the DB, check that this field is still equal to the user ID that it got passed in, EDIT: and, if it's different, make the result null. Manipulate the field using the methods on the Interlocked class to avoid the need to lock. That's wrong; reference types can be read and written atomically with Interlocked.

2nd EDIT: You could also return the original user ID from the background worker along with the result, and check that it's the most recent one clicked in your Completed handler.

Alternatively, if you're keeping references to all of the BackgroundWorkers, you could use their cancellation support.

SLaks
Why was this downvoted?
SLaks
There is no method in Interlocked which can help him compare string. Also, in my opinion BackgroundWorker should act on the input which was given to it. After processing the input, it should not check if the original input has changed and do some more processing.
SolutionYogi
You're right; I was thinking of value types
SLaks
You misunderstood my answer. I'm just saying that the background worker should check whether the field changed, and, if it did, suppress the BW's result.
SLaks
I removed the downvote. Also, I would rather return the 'userid' and the associated data from the BW. The calling code can check if the 'userid' returned from BW is same as what user selected last and use it if it wants to.
SolutionYogi
That's probably a good idea; it would require a new class, though. (Unless you use KeyValuePair or .Net 4's new Tuple type)
SLaks
A: 

I've used this to cancel BackgroundWorkers before and it worked great.

    public void cancelWorker( BackgroundWorker worker )
    {
        if (worker != null)
        {
            if (worker.IsBusy)
            {
                worker.CancelAsync();

                while (worker.IsBusy)
                {
                    Application.DoEvents();
                }
            }
        }
    }

I've heard controversy over using Application.DoEvents(); but I had problems with endless loops if I used Thread.sleep or other.

You might want to use polling of some sort so you don't end up with backgroundWorkers fumbling over eachother - especially if the callback ends up calling something like the UI on a separate thread so they can actually cause race conditions.

McAden
You shouldn't need to call CancelAsync more than once. You just need proper exit points in your BackgroundWorker code. So in your BackgroundWorker method, check if CancellationPending is true frequently (it's a cheap call). If it is true, then exit the method.
Steve Wortham
True, but as the comment says, I've actually found sometimes that the first worker.CancelASync() doesn't set CancellationPending - I've gotten into the loop and gotten stuck waiting for a BackgroundWorker that didn't know it was supposed to cancel...As in CancellationPending was still false after CancelAsync. You're right, I 'shouldn't' - yet I sat there and watched it happen in the debugger (CancellationPending continued to be false on a Cancellable BackgroundWorker). I didn't step into CancelAsync though.
McAden
I looked at the source code; there is no way that that can happen. All CancelAsync does is check WorkerSupportsCancellation and set the private boolean field; the field is only reset in AsyncOperationCompleted and RunWorkerAsync.
SLaks
That is weird, I haven't seen that behavior before. At first glance I do worry that your code may be an overly aggressive workaround. But if it works and it doesn't peg your processor to 100%, then maybe it's OK. I'd suggest for the OP to try calling CancelAsync just once at first, and seeing if that works before trying this method though.
Steve Wortham
I agree - it doesn't make sense. I was really confused as to why (it happened multiple times, and seemingly at random...it would get into Application.DoEvents(); loop while CancellationPending was false; I'm editing the 'if' out of the answer - I could have conflicting code I didn't notice).
McAden
+1  A: 

How about waiting for a second or two before you start your Background Worker?

Once the user clicks on a user id, start a timer with 1 second interval and after that one second, start your BackgroundWorker. If user clicks on a different user id, reset the timer. This way, if user keeps clicking on different user ids in quick succession, you won't do anything. Once user has taken a break, you start your background worker.

SolutionYogi