《Windows核心编程》中第11章讲到线程池的使用,可惜,总是看得不大懂。是翻译的问题还是我理解的问题?
于是,只好求助MSDN,找几篇关于线程池的文章来看看。找到一篇在.Net中线程池应用的,觉得还可以,不太难理解。MSDN默认的字体太小了,看得不太舒服,转过来。
-------------------------------------------------------
You can use thread pooling to make much more efficient use of multiple threads, depending on your application. Many applications use multiple threads, but often those threads spend a great deal of time in the sleeping state waiting for an event to occur. Other threads might enter a sleeping state and be awakened only periodically to poll for a change or update status information before going to sleep again. Using thread pooling provides your application with a pool of worker threads that are managed by the system, allowing you to concentrate on application tasks rather than thread management. In fact, if you have a number of short tasks that require more than one thread, using the ThreadPool class is the easiest and best way to take advantage of multiple threads. Using a thread pool enables the system to optimize this for better throughput(生产能力) not only for this process but also with respect to other processes on the computer, something your application will know nothing about. Using a thread pool enables the system to optimize thread time slices taking into account all the current processes on your computer.
The .NET Framework uses thread pools for several purposes: asynchronous calls, System.Net socket connections, asynchronous I/O completion, and timers and registered wait operations, among others.
You use the thread pool by calling ThreadPool.QueueUserWorkItem from managed code (or CorQueueUserWorkItem from unmanaged code) and passing a WaitCallback delegate wrapping the method that you want to add to the queue. You can also queue work items that are related to(涉及) a wait operation to the thread pool by using ThreadPool.RegisterWaitForSingleObject and passing a WaitHandle that, when signaled or when timed out, raises a call to the method wrapped by the WaitOrTimerCallback delegate. In both cases, the thread pool uses or creates a background thread to invoke the callback method.
You can also use the unsafe methods ThreadPool.UnsafeQueueUserWorkItem and ThreadPool.UnsafeRegisterWaitForSingleObject when you know that the caller's stack is irrelevant(不相关的) to any security checks performed during the execution of the queued task. QueueUserWorkItem and RegisterWaitForSingleObject both capture the caller's stack, which is merged(合并,并入) into the stack of the thread pool thread when the thread pool thread starts to execute a task. If a security check is required, that entire stack must be checked. Although the check provides safety, it also has a performance cost. Using the Unsafe method calls does not provide complete safety, but it will provide better performance.
There is only one ThreadPool object per process. The thread pool is created the first time you call ThreadPool.QueueUserWorkItem, or when a timer or registered wait operation queues a callback method. One thread monitors all tasks that have been queued to the thread pool. When a task has completed, a thread from the thread pool executes the corresponding(相应的) callback method. There is no way to cancel a work item after it has been queued.
The number of operations that can be queued to the thread pool is limited only by available memory; however, the thread pool will enforce a limit on the number of threads it allows to be active in the process simultaneously(同时地) (which is subject to the number of CPUs and other considerations). Each thread uses the default stack size, runs at the default priority, and is in the multithreaded apartment. If one of the threads becomes idle (as when waiting on an event) in managed code, the thread pool injects(注入) another worker thread to keep all the processors busy. If all thread pool threads are constantly busy, but there is pending work in the queue, the thread pool will, after some period of time, create another worker thread. However, the number of threads will never exceed the maximum value. The ThreadPool also switches to the correct AppDomain when executing ThreadPool callbacks.
There are several scenarios in which it is appropriate to create and manage your own threads instead of using the ThreadPool. You should do so:
- If you require a task to have a particular priority.
- If you have a task that might run a long time (and therefore block other tasks).
- If you need to place threads into a single-threaded apartment (all ThreadPool threads are in the multithreaded apartment).
- If you need to have a stable(稳定的) identity associated with the thread. For example, you might want to use a dedicated thread to abort that thread, suspend it, or discover it by name.
ThreadPool Examples
The three code examples that follow demonstrate the QueueUserWorkItem and RegisterWaitForSingleObject methods. The first example queues a very simple task, represented by the ThreadProc
method, using QueueUserWorkItem.
using
System;
using
System.Threading;
public
class
Example
{
public static void Main() {
// Queue the task.
ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc));
Console.WriteLine("Main thread does some work, then sleeps.");
// If you comment out the Sleep, the main thread exits before
// the thread pool task runs. The thread pool uses background
// threads, which do not keep the application running. (This
// is a simple example of a race condition.)
Thread.Sleep(1000);
Console.WriteLine("Main thread exits.");
}
// This thread procedure performs the task.
static void ThreadProc(Object stateInfo) {
// No state object was passed to QueueUserWorkItem, so
// stateInfo is null.
Console.WriteLine("Hello from the thread pool.");
}
}
Supplying Task Data for QueueUserWorkItem
The following code example uses QueueUserWorkItem to queue a task and supply the data for the task.
using
System;
using
System.Threading;
//
TaskInfo holds state information for a task that will be
//
executed by a ThreadPool thread.
public
class
TaskInfo
{
// State information for the task. These members
// can be implemented as read-only properties, read/write
// properties with validation, and so on, as required.
public string Boilerplate;
public int Value;
// Public constructor provides an easy way to supply all
// the information needed for the task.
public TaskInfo(string text, int number) {
Boilerplate = text;
Value = number;
}
}
public
class
Example
{
public static void Main() {
// Create an object containing the information needed
// for the task.
TaskInfo ti = new TaskInfo("This report displays the number {0}.", 42);
// Queue the task and data.
if (ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc), ti)) {
Console.WriteLine("Main thread does some work, then sleeps.");
// If you comment out the Sleep, the main thread exits before
// the ThreadPool task has a chance to run. ThreadPool uses
// background threads, which do not keep the application
// running. (This is a simple example of a race condition.)
Thread.Sleep(1000);
Console.WriteLine("Main thread exits.");
}
else {
Console.WriteLine("Unable to queue ThreadPool request.");
}
}
// The thread procedure performs the independent task, in this case
// formatting and printing a very simple report.
//
static void ThreadProc(Object stateInfo) {
TaskInfo ti = (TaskInfo) stateInfo;
Console.WriteLine(ti.Boilerplate, ti.Value);
}
}
RegisterWaitForSingleObject
The following example demonstrates several threading features.
- Queuing tasks for execution by ThreadPool threads, with RegisterWaitForSingleObject.
- Signaling the tasks to execute, with AutoResetEvent.
- Handling both time-outs and signals with a WaitOrTimerCallback delegate.
- Canceling a queued task with RegisteredWaitHandle.
using
System;
using
System.Threading;
//
TaskInfo contains data that will be passed to the callback
//
method.
public
class
TaskInfo
{
public RegisteredWaitHandle Handle = null;
public string OtherInfo = "default";
}
public
class
Example
{
public static void Main(string[] args) {
// The main thread uses AutoResetEvent to signal the
// registered wait handle, which executes the callback
// method.
AutoResetEvent ev = new AutoResetEvent(false);
TaskInfo ti = new TaskInfo();
ti.OtherInfo = "First task";
// The TaskInfo for the task includes the registered wait
// handle returned by RegisterWaitForSingleObject. This
// allows the wait to be terminated when the object has
// been signaled once (see WaitProc).
ti.Handle = ThreadPool.RegisterWaitForSingleObject(
ev,
new WaitOrTimerCallback(WaitProc),
ti,
1000,
false
);
// The main thread waits three seconds, to demonstrate the
// time-outs on the queued thread, and then signals.
Thread.Sleep(3100);
Console.WriteLine("Main thread signals.");
ev.Set();
// The main thread sleeps, which should give the callback
// method time to execute. If you comment out this line, the
// program usually ends before the ThreadPool thread can execute.
Thread.Sleep(1000);
// If you start a thread yourself, you can wait for it to end
// by calling Thread.Join. This option is not available with
// thread pool threads.
}
// The callback method executes when the registered wait times out,
// or when the WaitHandle (in this case AutoResetEvent) is signaled.
// WaitProc unregisters the WaitHandle the first time the event is
// signaled.
public static void WaitProc(object state, bool timedOut) {
// The state object must be cast to the correct type, because the
// signature of the WaitOrTimerCallback delegate specifies type
// Object.
TaskInfo ti = (TaskInfo) state;
string cause = "TIMED OUT";
if (!timedOut) {
cause = "SIGNALED";
// If the callback method executes because the WaitHandle is
// signaled, stop future execution of the callback method
// by unregistering the WaitHandle.
if (ti.Handle != null)
ti.Handle.Unregister(null);
}
Console.WriteLine("WaitProc( {0} ) executes on thread {1}; cause = {2}.",
ti.OtherInfo,
Thread.CurrentThread.GetHashCode().ToString(),
cause
);
}
}