Jerry
Jerry

Reputation: 4547

Using threads to count the loops in C# events

EDIT: It is not a listbox. My mistake. It is a list view.

I have a list view control that's driving me nuts. It is a multi-select list box, so if the user selects 5000 rows, then de-selects them by selecting a single row, the SelectedIndexChanged fires 5001 times. This causes my app to hang.

I'm trying to use threads to count the number of times that the event WOULD have fired, and then letting the last iteration do all the actual work.

Here's the code I started with. The big catch: I need the "do fancy calculations" to be in the same thread as the calling events due to items out of my control.

EDIT: I know that this code doesn't work. The Join() blocks the current thread which negates the entire purpose of creating the thread. My question is : How do I do something LIKE this.

My biggest problem isn't creating the thread. It's that my "do fancy" has to be in the same thread.

    void IncrPaintQueue()
    {
        PaintQueue++;
        Thread.Sleep(100);
    }

    int PaintQueue = 0;

    private void SegmentList_SelectedIndexChanged(object sender, EventArgs e)
    {
        // We need to know how many threads this may possibly spawn.
        int MyQueue = PaintQueue;

        // Start a thread to increment the counter.
        Thread Th = new Thread(IncrPaintQueue);
        Th.IsBackground = true;
        Th.Start();
        Th.Join();

        // if I'm not the last thread, then just exit. 
        // The last thread will do the right calculations.
        if (MyQueue != PaintQueue - 1)
            return;

        // Reset the PaintQueue counter.
        PaintQueue = 0;

        // ... do fancy calculations here...
    }

Upvotes: 2

Views: 1359

Answers (5)

Brian Rasmussen
Brian Rasmussen

Reputation: 116401

You don't really achieve any kind of concurrency by starting a new thread and then immediately Joining it. The only "effect" of the code above is that you method is run by another thread.

Additionally, if you want to use a background thread and safe the rather expensive cost of newing a thread, you should employ the thread pool.

Upvotes: 0

Michael Meadows
Michael Meadows

Reputation: 28416

I get the impression that you're trying to solve a problem through brute force. I would suggest trying a different event:

private void myListView_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
{
    if (e.IsSelected)
    {
        // do your logic here
     }
}

I would suggest avoiding creating threads if at all possible, since they have overheaad. I couldn't see from your example where there's any need for parallelism.

Upvotes: 1

Matt Brunell
Matt Brunell

Reputation: 10389

I remember solving this issue before:

A better way perhaps for you would be to put a minimal delay in your ItemSelectionChange Handler. Say -- 50ms. Use a timer, Once the selection changes, restart the timer. If the selection changed more than once within the delay period, then the original is ignored, but after the delay has expired, the logic is executed.

Like this:

public class SelectionEndListView : ListView
{
private System.Windows.Forms.Timer m_timer;
private const int SELECTION_DELAY = 50;

public SelectionEndListView()
{
   m_timer = new Timer();
   m_timer.Interval = SELECTION_DELAY;
   m_timer.Tick += new EventHandler(m_timer_Tick);
}

protected override void OnSelectedIndexChanged(EventArgs e)
{
   base.OnSelectedIndexChanged(e);

   // restart delay timer
   m_timer.Stop();
   m_timer.Start();
}

private void m_timer_Tick(object sender, EventArgs e)
{
   m_timer.Stop();

   // Perform selection end logic.
   Console.WriteLine("Selection Has Ended");
}
}

Upvotes: 2

Craig Gidney
Craig Gidney

Reputation: 18276

A possible solution is to delay the work, so you know whether or not more events have fired. This assumes the order of the selections is not important; all that matters is the current state.

Instead of doing the work as soon as the event fires, set up a timer to do it a couple milliseconds after the event fires. If the timer is already running, do nothing. In this way the user should perceive no difference, but the actions will not hang.

You could also do the work on another thread, but have a flag to indicate work is being done. If, when the selection event fires, work is still being done you set a flag that indicates the work should be repeated. Setting 'repeat_work' to true 5000 times is not expensive.

Upvotes: 1

casperOne
casperOne

Reputation: 74530

First, while you are properly synchronizing access to PaintQueue, I feel it was more by chance in this situation as opposed to design. If you have other code accessing PaintQueue on other threads, then you have a problem.

Second, this code makes no sense. You are spooling up a new thread, incrementing the value on that thread, and then waiting for 1/10th of a second. The thing is, the code that kicks off the thread is waiting on that thread to complete. Because of this, you are just waiting in the UI thread for nothing.

Even if you queue the SelectedIndexChange events, you aren't going to be able to prevent your app from hanging. The SelectedIndexChange event is going to fire every time that you select an item, and if the user selects 5000 items, then you need to process all 5000 events. You could give them a window (process every n seconds or whatever) but that's rather arbitrary and you put the user on a timer, which is bad.

What you should do is not tie the operation to the SelectedIndexChanged event. Rather, have the user select the items and then have them perform some other action (click a button, for example) which will work on the selected items.

Your app will still hang though if you have to process a number of items for a lengthy period of time on the UI thread, but at least selecting the items won't hang.

Upvotes: 0

Related Questions