Hi All,
I've been look for the solution for this problem for days.. any help is very much appreciated.
My project invovles a small task that requires to generate three concurrent threads, it looks like:
Code Block
Main()
{
........
//Start three threads;
Start Thread1;
Start Thread2;
Start Thread3;
//Wait for three threads to finish
Thread1.Join();
Thread2.Join();
Thread3.Join()
Messagebox.Show("All threads finished");
......
}
Thread1/2/3 ()
{
//some work here;
}
The code above works.. the only problem is that when the Main was waiting for three threads to finish, all components (eg.buttons)on the Main form were not responding..the Main form was locked...
I am wondering is there anyway to solve this problem?
I tried to use Thread.Sleep along with a While loop to wait:.
Code Block
Main()
{
........
//Start three threads;
Start Thread1;
Start Thread2;
Start Thread3;
threadFinished = 0;
//Wait for three threads to finish
While (threadFinished <3)
{
Thread.Sleep (5000);
}
Messagebox.Show("All threads finished");
......
}
Thread1/2/3 ()
{
//some work here;
lock (this)
{
threadFinished++;
}
}
But it didn't work either..
I wantthat components on the main form can still work while it's waiting for other threads to finish.
Thanks in advance
| | Firen Wednesday, December 05, 2007 1:46 PM | Well, I started just writing a small example, and it grew pretty large. Here you go anyway; hope you can use some of the ideas, but don't necessarily lock yourself into this architecture since I didn't really think about what I was doing much: It is not complete by any means. General idea for what the threads are doing: Start n threads, each assigned to download a specific file. When a thread is done, it raises an event that causes the FileList class to replace that thread with a new thread for a different file. Once the last thread is done, then FileList raises an event that it has completed. Note that FileList.Download is asynchronous (it will return after starting the downloads, but won't wait for them to finish). Possible improvements: - Reuse threads - Different class name (FileList sounds like a simple class, not a file list download thread manager class) - Many more...
Code Block
class FileList { private object syncLock = new object; private List<DownloadFile> fileList; private FileDownloaderThread[] downloadThreads; private boolean downloading = false; public event DownloadCompleteEventHandler DownloadComplete; public event ProgressChangedEventHandler ProgressChanged; public FileList(Item item) { // item is one of the "items" that the user can choose to download fileList = item.GetDownloadList(); } public virtual void Download(int maxDownloadThreads) { int numThreads = maxDownloadThreads; if (fileList.Count < numThreads) numThreads = fileList.Count; if (!downloading) { downloading = true; downloadThreads = new FileDownloaderThread[numThreads]; for (int i = 0; i < numThreads; i++) { initDownloadThread(i); } } } protected virtual void initDownloadThread(int index) { if (downloadThreads[index] != null) { FileDownloaderThread oldThread = downloadThreads[index]; oldThread.DownloadComplete -= new DownloadCompleteEventHandler(this.downloadComplete); // same for DownloadError and ProgressChanged } DownloadFile nextFile = getNextFile(); if (nextFile != null) { lock(syncLock) { nextFile.DownloadStatus = DownloadStatus.Starting; } FileDownloaderThread curThread = new FileDownloaderThread(); curThread.DownloadComplete += new DownloadCompleteEventHandler(this.downloadComplete); curThread.DownloadError += new DownloadErrorEventHandler(this.downloadError); curThread.ProgressChanged += new ProgressChangedEventHandler(this.progressChanged); curThread.StartDownload(nextFile, syncLock); downloadThreads[index] = curThread; } else { downloadThreads[index] = null; // possible bug, check on this: // I think unreferenced Threads can continue running, // but I'm not sure } } protected virtual DownloadFile getNextFile() { lock (syncLock) { // search through fileList to determine the next DownloadFile // with file.DownloadStatus == DownloadStatus.Unstarted // if none found, retry any errored ones // if all are complete, return null } } protected virtual int getNumberDownloading() { // implement } private void DownloadComplete(object sender, EventArgs e) { FileDownloaderThread thr = sender as FileDownloaderThread; if (thr != null) { initDownloadThread(thr.Index); // index into downloadThreads
// check if done downloading all files // we can either check the status of each file, or check // whether all download threads are null (the latter will be // faster for huge sets of files) bool done = true; for (int i = 0; i < downloadThreads.Count; i++) { if (downloadThreads[i] != null) { done = false; break; } } if (done) { OnDownloadComplete(EventArgs.Empty); } } } protected virtual void OnDownloadComplete(EventArgs e) { if (DownloadComplete != null) DownloadComplete(this, e); // note, called on the last thread to complete } }
class FileDownloaderThread { private DownloadFile file; private object syncLock; public event DownloadCompleteEventHandler DownloadComplete; public event ProgressChangedEventHandler ProgressChanged; public event DownloadErrorEventHandler DownloadError; public void StartDownload(DownloadFile file, object syncLock) { this.file = file; this.syncLock = syncLock; start thread this.downloadThread(); // use a normal thread or the threadpool } private void downloadThread() { file.Download(syncLock); // connect and download this.file // DownloadFile should contain the remote filename // and the local path to save to, etc. // in Download(), make sure to lock(syncLock) when changing file.DownloadStatus // since FileList.GetNextFile() will use this switch (file.DownloadStatus) { case DownloadResult.Success: OnDownloadComplete(EventArgs.Empty); break; case DownloadResult.CouldNotConnect: OnDownloadError(new DownloadErrorEventArgs(file.DownloadStatus)); break; // ... } } protected virtual void OnDownloadError(DownloadErrorEventArgs e) { if (DownloadError != null) DownloadError(this, e); // note, called on this thread } // same for OnDownloadComplete } | | sirjis Thursday, December 06, 2007 5:10 PM | if you want components to work, you probably dont want the thread for main to suspect or sleep.
what are you trying to accomplish? why do you want the main thread to wait for those work threads?
you can disable a few controls when the workers are running, re-enable them when workers are finished, the 'main' thread doesn't need to wait.
| | H. _冬_ Tony Wednesday, December 05, 2007 3:56 PM | You should use another thread to monitor the individual threads. This can be done using a BackgroundWorker, and in the RunWorkerCompleted method, notify the main form that the threads are done. The work of this thread should be in the DoWork event handler, and it should work fine to just have three Join statements like you did in the first example.
Another way that isn't normally quite as good is to add Application.DoEvents() calls in the waiting loop. You would want the Thread.Sleep command to be more like 100ms in that case so the form is still somewhat responsive.
Edit: Didn't see Tony's answer. That might be the best solution.
| | sirjis Wednesday, December 05, 2007 4:01 PM | Hi Tony and sirjis, thanks very much. My project is like a download manager. Firstly, it allows a user to choose several items to download, and actually each item is a list of 100-200 files. The name of these items (the filelist for the item is still unknown at this stage) will be passed into the main. In the main, I use a foreach loop to go through each item, firstly to generate the filelist corresponding to that item, and then generate three threads to download all files in that list queue. and I have to wait for those threads completing downloading all files in the list to continue my foreach loop (to process the next item). It might be more clear if I describe that in a brief code-like language
Code Block
Main(int ItemCollection[])
{ foreach (int item in itemCollection) { Generate the corresponding Filelist For This Item; Start Thread1/2/3 to process this List; Wait for all Threads to finish; Tell user one item has finished downloading. } } I understand that suspend or sleep the main thread will make the main windows frozen..but I don't know if there is another way that can acheive the same goal as I required If I don't make the main thread to wait, the foreach loop will quickly jump to the next item when the current item is not finished yet, and generate the filelist for the next item which will overwrite the current filelist, to make it worse.. it will generate threads again and again for each item. Thanks very much. | | Firen Wednesday, December 05, 2007 8:22 PM | Well, I started just writing a small example, and it grew pretty large. Here you go anyway; hope you can use some of the ideas, but don't necessarily lock yourself into this architecture since I didn't really think about what I was doing much: It is not complete by any means. General idea for what the threads are doing: Start n threads, each assigned to download a specific file. When a thread is done, it raises an event that causes the FileList class to replace that thread with a new thread for a different file. Once the last thread is done, then FileList raises an event that it has completed. Note that FileList.Download is asynchronous (it will return after starting the downloads, but won't wait for them to finish). Possible improvements: - Reuse threads - Different class name (FileList sounds like a simple class, not a file list download thread manager class) - Many more...
Code Block
class FileList { private object syncLock = new object; private List<DownloadFile> fileList; private FileDownloaderThread[] downloadThreads; private boolean downloading = false; public event DownloadCompleteEventHandler DownloadComplete; public event ProgressChangedEventHandler ProgressChanged; public FileList(Item item) { // item is one of the "items" that the user can choose to download fileList = item.GetDownloadList(); } public virtual void Download(int maxDownloadThreads) { int numThreads = maxDownloadThreads; if (fileList.Count < numThreads) numThreads = fileList.Count; if (!downloading) { downloading = true; downloadThreads = new FileDownloaderThread[numThreads]; for (int i = 0; i < numThreads; i++) { initDownloadThread(i); } } } protected virtual void initDownloadThread(int index) { if (downloadThreads[index] != null) { FileDownloaderThread oldThread = downloadThreads[index]; oldThread.DownloadComplete -= new DownloadCompleteEventHandler(this.downloadComplete); // same for DownloadError and ProgressChanged } DownloadFile nextFile = getNextFile(); if (nextFile != null) { lock(syncLock) { nextFile.DownloadStatus = DownloadStatus.Starting; } FileDownloaderThread curThread = new FileDownloaderThread(); curThread.DownloadComplete += new DownloadCompleteEventHandler(this.downloadComplete); curThread.DownloadError += new DownloadErrorEventHandler(this.downloadError); curThread.ProgressChanged += new ProgressChangedEventHandler(this.progressChanged); curThread.StartDownload(nextFile, syncLock); downloadThreads[index] = curThread; } else { downloadThreads[index] = null; // possible bug, check on this: // I think unreferenced Threads can continue running, // but I'm not sure } } protected virtual DownloadFile getNextFile() { lock (syncLock) { // search through fileList to determine the next DownloadFile // with file.DownloadStatus == DownloadStatus.Unstarted // if none found, retry any errored ones // if all are complete, return null } } protected virtual int getNumberDownloading() { // implement } private void DownloadComplete(object sender, EventArgs e) { FileDownloaderThread thr = sender as FileDownloaderThread; if (thr != null) { initDownloadThread(thr.Index); // index into downloadThreads
// check if done downloading all files // we can either check the status of each file, or check // whether all download threads are null (the latter will be // faster for huge sets of files) bool done = true; for (int i = 0; i < downloadThreads.Count; i++) { if (downloadThreads[i] != null) { done = false; break; } } if (done) { OnDownloadComplete(EventArgs.Empty); } } } protected virtual void OnDownloadComplete(EventArgs e) { if (DownloadComplete != null) DownloadComplete(this, e); // note, called on the last thread to complete } }
class FileDownloaderThread { private DownloadFile file; private object syncLock; public event DownloadCompleteEventHandler DownloadComplete; public event ProgressChangedEventHandler ProgressChanged; public event DownloadErrorEventHandler DownloadError; public void StartDownload(DownloadFile file, object syncLock) { this.file = file; this.syncLock = syncLock; start thread this.downloadThread(); // use a normal thread or the threadpool } private void downloadThread() { file.Download(syncLock); // connect and download this.file // DownloadFile should contain the remote filename // and the local path to save to, etc. // in Download(), make sure to lock(syncLock) when changing file.DownloadStatus // since FileList.GetNextFile() will use this switch (file.DownloadStatus) { case DownloadResult.Success: OnDownloadComplete(EventArgs.Empty); break; case DownloadResult.CouldNotConnect: OnDownloadError(new DownloadErrorEventArgs(file.DownloadStatus)); break; // ... } } protected virtual void OnDownloadError(DownloadErrorEventArgs e) { if (DownloadError != null) DownloadError(this, e); // note, called on this thread } // same for OnDownloadComplete } | | sirjis Thursday, December 06, 2007 5:10 PM | Thanks so much..sounds a good idea  , and I'll take a look into the code carefully tonight. this download manager is the last part of my project and it's been delaying the whole thing for weeks.  . Thanks again.  | | Firen Thursday, December 06, 2007 6:30 PM |
|