This message was discovered on microsoft.public.dotnet.framework.clr.
Responses highlighted in red are from those people who are likely to be able to contribute good, authoratitive information to this discussion. They include Microsoft employees, MVP's and others who IMHO contribute well to these kinds of discussions.
| Pierre Greborio |
| GOOD ANSWER |
Hi, I have a serious problem using HttpWebRequest on a ThreadPool context. After some calls I get the following error "There were not enough free threads in the ThreadPool object to complete the operation.". I investigated the behaviour of the ThreadPool and I've seen that the number of threads grows until it reach 60 threads (I have a dual PIII 800MHz processor). When the HttpWebRequest code is commented, that root is never exceed, whereas with the HttpWebRequest code it try to exceed that value, raising an exception. I have seen with ildasm that GetResponse() of HttpWebRequest uses an asynchrounous method call, then implicit ThreadPool. I have the same problem with HttpRequest class. Is it a bug ? Is there any workaround or other solution to may problem ?
At the end of the email I posted the code.
Thank you Pierre
using System; using System.IO; using System.Net; using System.Text; using System.Threading; using System.Net.Sockets;
namespace ThreadPoolAsynch { public class SrvPool { private Thread th = null; private bool run;
public SrvPool() { run = false; }
public void Start() { try { run = true; th = new Thread(new ThreadStart(Process)); th.Start(); } catch(Exception ex) { Console.WriteLine("Crawler", "Crawler.Start error: " + ex.Message); } }
public void Stop() { try { run = false; if(th != null) th.Join(10000); } catch(Exception ex) { Console.WriteLine("Start error: " + ex.Message); } }
private void Process() { while(run) { try { // Normally gets the uri from dbms Uri uri = new Uri("http://localhost/Stockquote.asmx"); ThreadPool.QueueUserWorkItem(new WaitCallback(this.Analyze), uri); } catch(Exception) { Console.WriteLine("Error starting the thread."); } } }
private void Analyze(Object o) { string pageContent; try { Uri uri = o as Uri; Console.WriteLine("Start: Thread {0} - uri = {1}", AppDomain.GetCurrentThreadId(), uri.AbsoluteUri);
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(uri); req.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; Q312461; .NET CLR 1.0.3705)"; HttpWebResponse res = (HttpWebResponse)req.GetResponse(); StreamReader sr = new StreamReader(res.GetResponseStream(), Encoding.UTF8); pageContent = sr.ReadToEnd(); res.Close(); sr.Close();
Console.WriteLine("Stop: Thread {0} - uri = {1} - page lenght = {2}", AppDomain.GetCurrentThreadId(), uri.AbsoluteUri, pageContent.Length); } catch(Exception ex) { Console.WriteLine("Error on ThreadID {0}: {1}", AppDomain.GetCurrentThreadId(), ex.Message); } } } }
|
|
|
| |
|
|
| |
| |
| Gedas Gudenas |
| GOOD ANSWER |
There are only 25 concurrent threads per processor, I suggest build some checking on how many threads you are running by using ThreadPool.GetMaxThreads and ThreadPool.GetAvailableThreads functions. There is a way to change the max concurrent threads, but I don't think you need that complication and from what I know it causes some performance issues. Here are couple articles you might find usefull.
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemthreadingthreadpoolclasstopic.asp
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndotnet/html/dotnetperftechs.asp
"Pierre Greborio" <Click here to reveal e-mail address> wrote in message news:eEV7Gjt2BHA.1004@tkmsftngp04... Hi, I have a serious problem using HttpWebRequest on a ThreadPool context. After some calls I get the following error "There were not enough free threads in the ThreadPool object to complete the operation.". I investigated the behaviour of the ThreadPool and I've seen that the number of threads grows until it reach 60 threads (I have a dual PIII 800MHz processor). When the HttpWebRequest code is commented, that root is never exceed, whereas with the HttpWebRequest code it try to exceed that value, raising an exception. I have seen with ildasm that GetResponse() of HttpWebRequest uses an asynchrounous method call, then implicit ThreadPool. I have the same problem with HttpRequest class. Is it a bug ? Is there any workaround or other solution to may problem ?
At the end of the email I posted the code.
Thank you Pierre
using System; using System.IO; using System.Net; using System.Text; using System.Threading; using System.Net.Sockets;
namespace ThreadPoolAsynch { public class SrvPool { private Thread th = null; private bool run;
public SrvPool() { run = false; }
public void Start() { try { run = true; th = new Thread(new ThreadStart(Process)); th.Start(); } catch(Exception ex) { Console.WriteLine("Crawler", "Crawler.Start error: " + ex.Message); } }
public void Stop() { try { run = false; if(th != null) th.Join(10000); } catch(Exception ex) { Console.WriteLine("Start error: " + ex.Message); } }
private void Process() { while(run) { try { // Normally gets the uri from dbms Uri uri = new Uri("http://localhost/Stockquote.asmx"); ThreadPool.QueueUserWorkItem(new WaitCallback(this.Analyze), uri); } catch(Exception) { Console.WriteLine("Error starting the thread."); } } }
private void Analyze(Object o) { string pageContent; try { Uri uri = o as Uri; Console.WriteLine("Start: Thread {0} - uri = {1}", AppDomain.GetCurrentThreadId(), uri.AbsoluteUri);
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(uri); req.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; Q312461; .NET CLR 1.0.3705)"; HttpWebResponse res = (HttpWebResponse)req.GetResponse(); StreamReader sr = new StreamReader(res.GetResponseStream(), Encoding.UTF8); pageContent = sr.ReadToEnd(); res.Close(); sr.Close();
Console.WriteLine("Stop: Thread {0} - uri = {1} - page lenght = {2}", AppDomain.GetCurrentThreadId(), uri.AbsoluteUri, pageContent.Length); } catch(Exception ex) { Console.WriteLine("Error on ThreadID {0}: {1}", AppDomain.GetCurrentThreadId(), ex.Message); } } } }
|
|
|
| |
|
|
| |
| |
| Willy Denoyette [MVP] (VIP) |
| GOOD ANSWER |
There are 25 threads per process in the pool, however a process can create a lot more threads.
Willy.
"Gedas Gudenas" <Click here to reveal e-mail address> wrote in message news:eqMmeo02BHA.1452@tkmsftngp05... There are only 25 concurrent threads per processor, I suggest build some checking on how many threads you are running by using ThreadPool.GetMaxThreads and ThreadPool.GetAvailableThreads functions. There is a way to change the max concurrent threads, but I don't think you need that complication and from what I know it causes some performance issues. Here are couple articles you might find usefull.
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemthreadingthreadpoolclasstopic.asp
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndotnet/html/dotnetperftechs.asp
"Pierre Greborio" <Click here to reveal e-mail address> wrote in message news:eEV7Gjt2BHA.1004@tkmsftngp04... Hi, I have a serious problem using HttpWebRequest on a ThreadPool context. After some calls I get the following error "There were not enough free threads in the ThreadPool object to complete the operation.". I investigated the behaviour of the ThreadPool and I've seen that the number of threads grows until it reach 60 threads (I have a dual PIII 800MHz processor). When the HttpWebRequest code is commented, that root is never exceed, whereas with the HttpWebRequest code it try to exceed that value, raising an exception. I have seen with ildasm that GetResponse() of HttpWebRequest uses an asynchrounous method call, then implicit ThreadPool. I have the same problem with HttpRequest class. Is it a bug ? Is there any workaround or other solution to may problem ?
At the end of the email I posted the code.
Thank you Pierre
using System; using System.IO; using System.Net; using System.Text; using System.Threading; using System.Net.Sockets;
namespace ThreadPoolAsynch { public class SrvPool { private Thread th = null; private bool run;
public SrvPool() { run = false; }
public void Start() { try { run = true; th = new Thread(new ThreadStart(Process)); th.Start(); } catch(Exception ex) { Console.WriteLine("Crawler", "Crawler.Start error: " + ex.Message); } }
public void Stop() { try { run = false; if(th != null) th.Join(10000); } catch(Exception ex) { Console.WriteLine("Start error: " + ex.Message); } }
private void Process() { while(run) { try { // Normally gets the uri from dbms Uri uri = new Uri("http://localhost/Stockquote.asmx"); ThreadPool.QueueUserWorkItem(new WaitCallback(this.Analyze), uri); } catch(Exception) { Console.WriteLine("Error starting the thread."); } } }
private void Analyze(Object o) { string pageContent; try { Uri uri = o as Uri; Console.WriteLine("Start: Thread {0} - uri = {1}", AppDomain.GetCurrentThreadId(), uri.AbsoluteUri);
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(uri); req.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; Q312461; .NET CLR 1.0.3705)"; HttpWebResponse res = (HttpWebResponse)req.GetResponse(); StreamReader sr = new StreamReader(res.GetResponseStream(), Encoding.UTF8); pageContent = sr.ReadToEnd(); res.Close(); sr.Close();
Console.WriteLine("Stop: Thread {0} - uri = {1} - page lenght = {2}", AppDomain.GetCurrentThreadId(), uri.AbsoluteUri, pageContent.Length); } catch(Exception ex) { Console.WriteLine("Error on ThreadID {0}: {1}", AppDomain.GetCurrentThreadId(), ex.Message); } } } }
|
|
|
| |
|
|
| |
|
|
| |
| Brian Combs |
| GOOD ANSWER |
Hello I modifided the ThreadPool sample (PoolsCS) that comes with the .Net SDK to use HttpWebRequest and HttpWebResponse. I changed it so it would create 20 threads. To test each request was to the same asp page that just returned the time in seconds so I could see that it was working. I had no trouble running this code on Windows XP Pro with VS.Net release and sp1. I had not thread errors and was able to see that all 20 threads returned with no errors. Here is the code I tested with.
using System; using System.Threading; using System.Net; using System.IO; using System.Text;
class App { public static void Main() { //we need to set the number of Http connections to allow the default is 2 ServicePointManager.DefaultConnectionLimit = 20; Console.WriteLine("Main thread: Queuing an asynchronous operation."); AutoResetEvent asyncOpIsDone = new AutoResetEvent(false); for (int i = 0; i < 20; i++) { ThreadPool.QueueUserWorkItem(new WaitCallback(MyAsyncOperation), asyncOpIsDone); }
Console.WriteLine("Main thread: Performing other operations."); // ...
Console.WriteLine("Main thread: Waiting for asynchronous operation to complete."); asyncOpIsDone.WaitOne(); Console.WriteLine("Asynchronous operation done"); }
// The callback method's signature MUST match that of a System.Threading.WaitCallback // delegate (it takes an Object parameter and returns void) static void MyAsyncOperation(Object state) { //Console.WriteLine("WorkItem thread: " + AppDomain.GetCurrentThreadId() + " Performing asynchronous operation."); // call an asp page that just returns the current time and then do writeline on that. HttpWebRequest WReq = (HttpWebRequest)WebRequest.Create("http://servername/time.asp"); WReq.Method = "GET"; HttpWebResponse WResp = (HttpWebResponse)WReq.GetResponse(); if (200 == (int)WResp.StatusCode) { //Read the raw HTML from the request StreamReader sr = new StreamReader(WResp.GetResponseStream(), Encoding.ASCII); //Convert the stream to a string string s = sr.ReadToEnd(); sr.Close(); Console.WriteLine("Data from request: " + s); } WResp.Close(); Console.WriteLine("Thread " + AppDomain.GetCurrentThreadId() + " has finished"); // ... // let the WriteLine have time to write. Thread.Sleep(200); // Sleep for 5 seconds to simulate doing work
// Signal that the async operation is now complete. ((AutoResetEvent)state).Set(); } }
Thanks Brian [MS] Microsoft Developer Support This posting is provided "AS IS" with no warranties, and confers no rights.
|
|
|
| |
|
|
| |
| |
| Pierre Greborio |
| GOOD ANSWER |
Hi Brain, I have no problem also if I set the number of request to 20. The problem raise when I have a while(true) cycle (run until there are uri to download). After some period I get an error message for ThreadPool. I get the same problem on your code changing the for statement with the while.
Thank you for your help. Pierre
"Brian Combs" <Click here to reveal e-mail address> wrote in message news:5$ONBh22BHA.2164@cpmsftngxa10... [Original message clipped]
|
|
|
| |
|
|
| |
| |
| Willy Denoyette [MVP] (VIP) |
| GOOD ANSWER |
It looks like the BeginGetResponse call throws following exception when the threadpool is low, I guess this is done to prevent deadlocks as it's called by the synchronous GetResponse method.
Unhandled Exception: System.InvalidOperationException: There were not enough free threads in the ThreadPool object to complete the operation. at System.Net.HttpWebRequest.BeginGetResponse(AsyncCallback callback, Object state) at System.Net.HttpWebRequest.GetResponse() at App.MyAsyncOperation(Object state)
This should definitely be documented. I would suggest you inspect the number of free threads in the pool using ThreadPool.GetAvailableThreads(out xxx, out yyy), and restrict the number of Workitems posted.
Willy.
"Pierre Greborio" <Click here to reveal e-mail address> wrote in message news:eHa0HW52BHA.2840@tkmsftngp05... [Original message clipped]
|
|
|
| |
|
|
| |
|
| |
| Brian Combs |
| GOOD ANSWER |
Hello I am unsure what you mean by "The problem raise when I have a while(true) cycle (run until there are uri to download). After some period I get an error message for ThreadPool. I get the same problem on your code changing the for statement with the while."
I don't understand why you would want a while loop to continue to run and create threads with no control over the number of threads it created. The idea of a thread pool is to have a number of threads that wait for some something and then go do the work. In your case the problem is that the HttpWebResponse object is using threads from the threadpool because it is making async IO calls on it's own. So it will use up some of the threads in the threadpool which is what is causing you to run out of threads when you create around 25 threads in the threadpool ( was able to get the error when I changed to loop number to 25) . You can also switch to using async Webrequest classes. However you should still limit the number of concurrent calls that are happening at any one time.
So to workaround the thread error you are getting you will need to limit the number of threads you create in your app.
Thanks Brian [MS] Microsoft Developer Support This posting is provided "AS IS" with no warranties, and confers no rights.
|
|
|
| |
|
|
| |
| |
| Willy Denoyette [MVP] (VIP) |
| GOOD ANSWER |
Brian,
Do you mean you should inspect the number of free threads in the pool before posting a Work Item? This can be problematic because you don't (and you shouldn't) know exactly how many threads are required by the framework when executing a work item.
IMO this not how the threadpool was meant to work, the thread pool should only enforce a limit on the number of threads to be active, but the number of work items to be queued to the pool could be much larger and is only (theoretically) limited by available memory. And this is how it works with other asynch operations (fi. file IO, sockets), but it fails in this particular case using HttpWebRequest.GetResponse.
Willy.
"Brian Combs" <Click here to reveal e-mail address> wrote in message news:nukr#FA3BHA.1596@cpmsftngxa08... [Original message clipped]
|
|
|
| |
|
|
| |
|
| |
| Pierre Greborio |
| GOOD ANSWER |
Hi Brian, Why should I use thread pool in your case ? I use the threadpool because the number of threadworkers is defined by the system (number of prcessor, memory, ecc.), not at design time.
Pierre
[Original message clipped]
|
|
|
| |
|
|
| |
| |
| Brian Combs |
| GOOD ANSWER |
Hello I did some checking on this problem with the ThreadPool class and HttpWebResponse class. I found quite a few old posts from the beta where this problem was reported. I looks like when you hit around 22 threads in the ThreadPool class and the callback function is using HttpWebResponse that you get the out of threads error. After doing some more digging it looks like this is a bug with the HttpWebResonse and how it interacts with the ThreadPool class. They are looking at fixing this for the next release. One workaround that was given when using the HttpWebResponse class with a ThreadPool was to limit the number of calls to the threadpool. The other was to implement your own thread pool.
I did not test to see if I use async HttpWebRequest with async I/0 to read the response and not use a thread pool it that would work.
Hope this helps. Thanks Brian [MS] Microsoft Developer Support This posting is provided "AS IS" with no warranties, and confers no rights.
|
|
|
| |
|
|
| |
| |
| Gert Driesen |
| GOOD ANSWER |
Brian,
What do you mean by "the next release" ?
Is it .NET Framework SP2 (and VS?NET SP1) or .NET Framework version 2 ?
Thanks,
Gert
"Brian Combs" <Click here to reveal e-mail address> wrote in message news:53f8pfN3BHA.1180@cpmsftngxa07... [Original message clipped]
|
|
|
| |
|
|
| |
| |
| Brian Combs |
| GOOD ANSWER |
Hello Next release is next major version. Remember that they may decide not to change this. I was basing the information in my post from some old beta newsgroup posts.
Thanks Brian [MS] Microsoft Developer Support This posting is provided "AS IS" with no warranties, and confers no rights.
|
|
|
| |
|
|
| |
|
|
|
|
|
|
| |
| John Giblin |
| GOOD ANSWER |
I have a project, which I will need to make 200,000 url requests. Seeing all the problems with the thread pool and httprequest. Does anyone have an suggestion on how to do this making multi-threaded.
John
"Brian Combs" <Click here to reveal e-mail address> wrote in message news:5$ONBh22BHA.2164@cpmsftngxa10... [Original message clipped]
|
|
|
| |
|
|
| |
| |
| Brian Combs |
| GOOD ANSWER |
Hello You can always create your own thread pool. However 200,000 is a lot of requests. You may want to think about doing this in C++ using sockets and creating your own threads to try and get better performance.
Thanks Brian [MS] Microsoft Developer Support This posting is provided "AS IS" with no warranties, and confers no rights.
|
|
|
| |
|
|
| |
|
| |
| Chuck Byram |
| GOOD ANSWER |
John,
I included what I did below and it seems to work.
WARNING: I cut out a lot of my actual code for readability and because it was off-topic, so this code may not work AS-IS --it's more for demonstrating the mechanism.
The problem with using ThreadPool.GetAvailableThreads is that if you're in a loop queuing things up, your threads may not have cranked up yet, so it may look like there are plenty of threads while you're in the loop, but once the threads get going, it drops fast. It looks like we'd be fine if we didn't call UploadData or whatever, which doesn't seem to play nicely with the ThreadPool.
To limit this problem, I just decided that I didn't want to start more than 12 threads at a time (12 being slightly less than half of the default max threads). I keep track of the number of started threads with m_NumThreadsStarted. I loop if it's greater than 12 and wait for a thread to decrement it right before the end of the thread proc. Once it's below 12, I increment it again and queue up another guy. Since the decrement in the thread happens before the thread terminates, there's a small hole between decrement and termination where my main wait loop could exit and generate another thread, but it seems unlikely that it would cause a problem. If you're concerned, bump 12 down to 6 or something.
using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.ServiceProcess; using System.Threading; using Microsoft.Win32; using System.Data.SqlClient; using System.Web; using System.Web.Mail; using System.Net; using System.Text;
namespace NetFocus.NetFocusUtilities { public class HTTPRequest { private int m_NumThreadsStarted;
public void SendHTTPRequests(DataSet ds) { try { foreach (DataRow dr in ds.Tables["tblHTTPRequest"].Rows) { HTTPRequestThreadData threadData = new HTTPRequestThreadData();
threadData.dr = dr; threadData.httpRequest = this;
while (m_NumThreadsStarted >= 12) { Thread.Sleep(50); } Interlocked.Increment(ref m_NumThreadsStarted); ThreadPool.QueueUserWorkItem(new WaitCallback(SendHTTPRequest), threadData); } } catch (Exception e) { //Logger.WriteEntry("Error in SendHTTPRequests: " + e.ToString()); } }
private static void SendHTTPRequest(Object state) { HTTPRequestThreadData threadData = (HTTPRequestThreadData)state; DataRow dr = threadData.dr; HTTPRequest httpRequest = threadData.httpRequest;
WebClient wc = new WebClient(); wc.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
try { string URL = dr["URL"].ToString(); string method = dr["NotificationMethod"].ToString(); byte[] postData = Encoding.ASCII.GetBytes(dr["Data"].ToString());
byte[] responseData = null;
try { responseData = wc.UploadData(URL, method, postData); } catch (WebException we) { //Logger.WriteEntry("Error opening HTTPRequest stream for '" + notificationID.ToString("B") + "' (" + URL + "): " + we.ToString() + // "\nWeb server response: " + we.Response); throw we; } catch (UriFormatException ufe) { //Logger.WriteEntry("Invalid URL format (" + URL + ") for '" + notificationID.ToString("B") + "': " + ufe.ToString()); throw ufe; }
wc.Dispose();
string response = Encoding.ASCII.GetString(responseData); } catch (WebException) { } catch (UriFormatException) { } catch (Exception e) { //Logger.WriteEntry("Error processing HTTPRequest " + notificationID.ToString("B") + ": " + e.ToString()); }
Interlocked.Decrement(ref httpRequest.m_NumThreadsStarted); }
private class HTTPRequestThreadData { public DataRow dr; public HTTPRequest httpRequest; } } }
"John Giblin" <Click here to reveal e-mail address> wrote in message news:OkTM4uGHCHA.2412@tkmsftngp11... [Original message clipped]
|
|
|
| |
|
|
| |
| |
| Chuck Byram |
| GOOD ANSWER |
Another, more elaborate method of plugging that hole would be to create an 12 element Thread array as a HTTPRequest member variable. Then, pass a reference to one of the slots in the thread data object to the thread. The thread populates the reference with Thread.CurrentThread. The mainline could be scanning the thread array looking for null or a ThreadState of Stopped to ensure that he has no more than 12 started at a time before starting another one.
Chuck Byram Click here to reveal e-mail address
"Chuck Byram" <Click here to reveal e-mail address> wrote in message news:e_7Y8.35748$Click here to reveal e-mail address... [Original message clipped]
|
|
|
| |
|
|
| |
| |
| Roger |
| GOOD ANSWER |
I am creating 5 to 10 threads only, and within each thread I am doing a WebRequest.GetResponse. Some calls are completed, but some hit the timeout. After a while, I start getting the There were not enough free threads in the ThreadPool .... error.
It is as if WebRequest is leaving active threads out there. How can I find out? How can I force WebRequest to continue using the same threads it is creating for each of MY threads that are using it?
-------------------------------- From: Roger Guess
|
|
|
| |
|
|
| |
|
| |
| Roger |
| GOOD ANSWER |
I am creating 5 to 10 threads only, and within each thread I am doing a WebRequest.GetResponse. Some calls are completed, but some hit the timeout. After a while, I start getting the There were not enough free threads in the ThreadPool .... error.
It is as if WebRequest is leaving active threads out there. How can I find out? How can I force WebRequest to continue using the same threads it is creating for each of MY threads that are using it?
-------------------------------- From: Roger Guess
|
|
|
| |
|
|
| |
|
| |
| Roger |
| GOOD ANSWER |
I am creating 5 to 10 threads only, and within each thread I am doing a WebRequest.GetResponse. Some calls are completed, but some hit the timeout. After a while, I start getting the There were not enough free threads in the ThreadPool .... error.
It is as if WebRequest is leaving active threads out there. How can I find out? How can I force WebRequest to continue using the same threads it is creating for each of MY threads that are using it?
-------------------------------- From: Roger Guess
|
|
|
| |
|
|
| |
|
| |
| Roger |
| GOOD ANSWER |
I am creating 5 to 10 threads only, and within each thread I am doing a WebRequest.GetResponse. Some calls are completed, but some hit the timeout. After a while, I start getting the There were not enough free threads in the ThreadPool .... error.
It is as if WebRequest is leaving active threads out there. How can I find out? How can I force WebRequest to continue using the same threads it is creating for each of MY threads that are using it?
-------------------------------- From: Roger Guess
|
|
|
| |
|
|
| |
|
| |
| Roger |
| GOOD ANSWER |
I am creating 5 to 10 threads only, and within each thread I am doing a WebRequest.GetResponse. Some calls are completed, but some hit the timeout. After a while, I start getting the There were not enough free threads in the ThreadPool .... error.
It is as if WebRequest is leaving active threads out there. How can I find out? How can I force WebRequest to continue using the same threads it is creating for each of MY threads that are using it?
-------------------------------- From: Roger Guess
|
|
|
| |
|
|
| |
|
|
|
|
|
|
|
|
|