views:

90

answers:

6

I have a thread that gathers a list of URLs from a website and updates the UI as it's doing so. That works fine. But, I need the Main thread to wait until the links have been gathered. I tried to do a join, but this locks up the UI. Here is my example. As you can see at the moment the foreach loop gets called at the same time as the thread is running. I want the foreach to run after the thread has ran.

Any ideas?

/** This thread will add links to list<string> linkList **/
Thread linkListThread = new Thread(new ThreadStart(getLinkList));
linkListThread.Start();
foreach (String link in linkList)
{
    txtOutput.Text += link;
}
+6  A: 

You can use a background worker. Or have the thread method call a method in main context when it is done, passing the list of items you gathered.

EDIT
I thought I should elaborate my second approach a little.

You could prepare a list instance before creating the thread:

List<string> links = new List<string>();

Then you pass this list to the thread, which fills it:

Thread t = new Thread(new ParameterizedThreadStart(FillList));
t.Start(links);

The thread method takes the list, fills it and calls a method that shows the details in the UI:

private static void FillList(object state)
{
    List<string> links = (List<string>)state;

    // Fill data

    this.Invoke((MethodInvoker)delegate() { HandleNewLinks(links); }));
}

The HandleNewLinks method works as one would expect:

private void HandleNewLinks(List<string> links)
{
    foreach (string link in links)
        // Do something...
}
Thorsten Dittmar
+1 - this is what I was trying to say, so I deleted my answer. Though don't forget you'll need to make the handler thread safe.
ChrisF
Of course, but I'm assuming in this sample that the thread is only created once to decouple filling the list and showing the result. My favourite solution would be a background worker anyway...
Thorsten Dittmar
A: 

It is not clear what you want: either the application waits (and is unresponsive), or the application does not wait and remains responsive. In the latter case, you might want to disable some controls / possible actions until the list has finished loading.

A dirty workaround is to do some sort of spin waiting (Join with a timeout of a few ms, returns the result whether it is done or not) with some Application.DoEvents() calls.

Lucero
I want the app to remain responsive yes. Would I need to create 2 threads and join them? Ie. the thread to gather the links, and a thread to output?Will join wait until the thread has fully completed?
James Jeffery
The UI thread will do the output (you should not access WinForms Controls from another thread), and the other thread does the work (getting the links). So the UI thread has a `while (!workerThread.Join(20)) { Application.DoEvents(); }` which will wait until the other thread is done while keeping the message pump (and thereby the UI) running.
Lucero
A: 

Something simple would be to have your worker threads call back to the main application on completion, you then keep a count of completed threads and in your main UI do something like:

while(runningThreads != 0)
{
    Application.DoEvents();
}

and have the threads call:

void OnThreadCompleted()
{
    runningThreads--;
}

Better to use BackgroundWorker for this instead of creating your own threads as this has all the callback mechanisms ready to go.

Paolo
+1  A: 

Move the code that needs to run after the thread has completed into an event handler for BackgroundWorker.RunWorkerCompleted

Update: The handler is invoked on the right (calling ) thread - so you can safely update the UI.

See the code snippet on the above msdn page.

Gishu
No, the RunWorkerCompleted code is called in the form's thread context by the background worker automatically.
Thorsten Dittmar
The method I'm calling ... getLinkList(), would that need to be changed to private void getLinkList(Object sender,DoWorkEventArgs args) ?So I can call bw.DoWork += getLinkList; ?
James Jeffery
Yes. However, you could as well double-click the DoWork event in the designer and copy the code from getLinkList() into the newly created event handler. Don't forget: Never handle exceptions in DoWork! They'll passed on to the RunWorkerCompleted event in e.Error.
Thorsten Dittmar
Thanks! I'll have to get reading up on this. Seems alot better than handling the thread directly.
James Jeffery
@Thorsten : thanks for clearing that up.
Gishu
A: 

We have used the Background worker for something similar and it worked well, with two observations:

  1. Don't append text to a textbox with += because it will slow you down considerably after a few hundred lines. Use AppendText instead.

  2. If you add a lot of info to the interface and have sleep times (during processing), the thread might 'fall asleep'. We fixed it by deleting the text in the textbox every 200 lines (the results were written to a file, so we didn't lose anything).

Rox
A: 

One alternative is simply use Invoke on the main thread:

void YourMethod()
{
    Thread linkListThread = new Thread(new ThreadStart(getLinkList));
    linkListThread.Start();
}

void getLinkList()
{
    List<string> linkList = new List<string>();
    // Your tasks

    // Done
    LinkListComplete(linkList);
}

void LinkListComplete(List<string> linkList)
{
    if (InvokeRequired)
    {
        Invoke(new Action<List<string>>(LinkListComplete),linkList);
        return;
    }

    foreach (String link in linkList)
    {
        txtOutput.Text += link;
    }
}
Chris S