在使用ThreadPool的时候,我们可能会经常使用到QueueUserWorkItem这个静态方法,这个方法的两个版本如下
public static bool QueueUserWorkItem(
WaitCallback callBack
)
public static bool QueueUserWorkItem(
WaitCallback callBack,
Object state
)
这两个版本都使用了WaitCallback的一个委托对象作为方法参数。
[ComVisibleAttribute(true)]
public delegate void WaitCallback(
Object state
)
而WaitCallback委托要求的委托签名带有一个object的参数。但有时候我们的实际需要可能不是一个带参数的WaitCallback函数。因此类似:
public delegate void WaitCallback()
这样的需求开始出现。
而了解WaitCallback通常又是不必要的,我们只想把代码并行执行。仅此而已。
下面这段代码是来自http://msdn.microsoft.com/msdnmag/issues/07/10/Futures/default.aspx?loc=zh的一段代码。
static void ParQuickSort<T>(T[] domain, int lo, int hi) where T : IComparable<T>
{
if (hi - lo <= Threshold) InsertionSort(domain, lo, hi);
int pivot = Partition(domain, lo, hi);
Parallel.Do(
delegate { ParQuickSort(domain, lo, pivot - 1); },
delegate { ParQuickSort(domain, pivot + 1, hi); }
);
}
以上代码中的Parallel.Do则是实现了将其委托参数作为并行计算的不同步骤进行并行运算的一个方法(它包含在MS Parallel Framework System.Concurrency中)。但是我们在.NET2.0或者哪怕是新出的.NET3.5framework中我们仍然无法找到它们。那么用旧有的线程池技术我们也来实现相似的功能。
我们有三个问题要解决:
1、从public delegate void WaitCallback( Object state )向public delegate void WaitCallbackNew()转变;
2、从public static bool QueueUserWorkItem( WaitCallback callBack )向public static bool QueueUserWorkItem( WaitCallbackNew callBack )转变;
3、从public static bool QueueUserWorkItem( WaitCallbackNew callBack )向public static bool QueueUserWorkItem( params WaitCallbackNew[] callBack )转变;
因此,首先我们定义了新的委托:
public delegate void WaitCallbackNew();
再次,我们增加我们的新方法:
public static bool QueueUserWorkItem(WaitCallbackNew callback)
{
}
这里我们要实现的是类似ThreadPool.QueueUserWorkItem(WaitCallback callback);的效果,但这里的callback由于参数不匹配,我们暂无法实现直接将委托采用类似重载的技术将其直接作为参数传入ThreadPool.QueueUserWorkItem(WaitCallback callback);但是很快我们注意到,ThreadPool.QueueUserWorkItem有一个重载,ThreadPool.QueueUserWorkItem(WaitCallback callback,Ojbect state);这个state的作用就是一个可以传递给WaitCallback的一个参数,因此这里我们应该换一个思路,而不是类似重载一样直接套用。将代码添加进上面的方法体内,于此同时我们应该构建一个用于WaitCallback的方法体用于满足我们对WaitCallback的依赖以及提供一个执行新委托的环境。
static void Callback(object state)
{
(state as WaitCallbackNew)();
}
public static bool QueueUserWorkItem(WaitCallbackNew callback)
{
return ThreadPool.QueueUserWorkItem(new WaitCallback(Callback), callback);
}
这样我们的代码基本上就可以实现类似:
// Queue the task.
ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc));
ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc));
ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc));
//
//The code above will be predigested to the code down!
//
ThreadPoolHelper.QueueUserWorkItem(ThreadProc);
ThreadPoolHelper.QueueUserWorkItem(ThreadProc);
ThreadPoolHelper.QueueUserWorkItem(ThreadProc);
// This thread procedure performs the task.
static void ThreadProc()
{
Console.WriteLine("Hello from the thread pool.()");
}
// This thread procedure performs the task.
static void ThreadProc(object stateInfo)
{
Console.WriteLine("Hello from the thread pool.(object stateInfo)");
}
这样的功能了。其中第一套方案为MSDN用于描述ThreadPool的QueueUserWorkItem方法的。而第二套方案则实现了无参数方法的委托。因此static void ThreadProc() 可以被正确地调用,而不必像static void ThreadProc(object stateInfo) 一样提供无畏的stateInfo而始终无法应用到。
最后,我们需要对这个ThreadPoolHelper.QueueUserWorkItem进行改进,使其支持params,也只有这样才能实现类似上面二分算法的外部表现。
增加以下代码:
public static bool QueueUserWorkItems(params WaitCallbackNew[] proc)
{
bool result = true;
foreach (WaitCallbackNew tp in proc)
{
result &= QueueUserWorkItem(tp);
}
return result;
}
如此就可以实现形如:
ThreadPoolHelper.QueueUserWorkItems(ThreadProc, ThreadProc, ThreadProc);
再加上.NET2.0以上版本对匿名委托的支持,就可以有:
ThreadPoolHelper.QueueUserWorkItems(
delegate { Console.WriteLine("a"); },
delegate { Console.WriteLine("b"); },
delegate { Console.WriteLine("c"); }
);
这样的代码了。
在多线程编程中,任务被分配后缺乏反馈,形象的比喻是你派了10个人去干活,最后谁干好谁没干好,默认是没有通知的。因此如果把你扣除多线程部分的当作你(主线程)的话,分配完任务后,你就有理由做你该做的其他事了。
但事实上,很多情况我们需要的是带反馈的编程,或者说,我们需要知道我们分配的任务是否完成了。
1、等待所有任务完成,方可后续处理。(WaitAll())
假设我们希望能够得到一个Expression的值Result = Add(5,3)+Sub(2,3)-Mul(23,2)*Div(19,3);而我们又希望其中的各个函数能够被分配到不同的线程上同步处理,这时候我们显然需要等待所有的函数都计算完成后才能够计算出Result的值。
2、等待任意任务完成,方可后续处理。(WaitAny())
假设现在我有一道难题需要一个答案,我找了10个高手为我作答,我的答案顶多也就是只求存在,不求最好。只要有一位高手为我解答了这道难题,我将进行后续的处理,而其他9位,我也只能谢谢你们付出了劳动了。
3、等待一段时间后,方可后续处理。(Sleep())
Sleep()是主线程所拥有的方法,目的旨在告诉你,我的忍耐是有限度的,时间到了,就不管你们了。当然了,如果时间到了,而主线程仍在运行,那么其他线程并没有终止。这一点和考试很像,很多人在交卷时间已经过了仍然在作答,老师没走的话,一般考卷还是可以交付的,但是这并不代表老师走的时候你一定能做完,或者说你做完了考卷不一定会有人要。(这个比喻比较生硬,大家可以调试下面的例子)。
当然,我们有理由指定一个线程,当它完成的时候就可以进行后续操作,不过这功能在当前的Helper中并没有,这一点出于两个原因,本Helper并不是一个权威通用的Helper,它旨在演示简单的多线程编程,它所能完成的任务也十分单一,如果引入WaitOne操作的话可能会带来不必要的功能浪费。再者WaitOne已经足够简单,甚至我并不推荐您在日常编程中使用本类,因为这可能带来您整个多线程编程上的风格不统一,但如果您的需求足够简单,我想本类起码能够节约您大部分的时间用于关注逻辑。
全部示例:
//ThreadPoolHelper.cs
using System;
using System.Threading;
using System.Collections.Generic;
namespace CA_ThreadPool
{
/// <summary>
/// Sample helper class for ThreadPool.
/// </summary>
class ThreadPoolHelper
{
/// <summary>
/// The delegate for explicit method.
/// </summary>
public delegate void WaitCallbackNew();
/// <summary>
/// The WaitCallback delegate object in actual of our thread pool.
/// </summary>
/// <param name="state"></param>
static void Callback(object state)
{
WaitCallbackHelper wcbh = (state as WaitCallbackHelper);
wcbh.Callback();
(wcbh.WaitHandle as AutoResetEvent).Set();
}
/// <summary>
/// Queues a method for execution.
/// The method executes when a thread pool thread becomes available.
/// </summary>
/// <param name="proc"></param>
/// <returns></returns>
public static bool QueueUserWorkItem(WaitCallbackNew callback)
{
WaitCallbackHelper wcbh = new WaitCallbackHelper();
wcbh.Callback = callback;
wcbh.WaitHandle = new AutoResetEvent(false);
if (AutoResetEvents == null)
{
AutoResetEvents = new List<WaitHandle>();
}
AutoResetEvents.Add(wcbh.WaitHandle);
return ThreadPool.QueueUserWorkItem(new WaitCallback(Callback), wcbh);
}
/// <summary>
/// Queues a few method for execution.
/// The method executes when a thread pool thread becomes available.
/// </summary>
/// <param name="proc"></param>
/// <returns></returns>
public static bool QueueUserWorkItems(params WaitCallbackNew[] proc)
{
bool result = true;
foreach (WaitCallbackNew tp in proc)
{
result &= QueueUserWorkItem(tp);
}
return result;
}
/// <summary>
/// (Collection)Notifies a waiting thread that an event has occurred. This class cannot be inherited.
/// </summary>
private static List<WaitHandle> AutoResetEvents;
/// <summary>
/// Waits for all the elements in the specified array to receive a signal.
/// </summary>
/// <returns>Waits for all the elements in the specified array to receive a signal.</returns>
public static bool WaitAll()
{
return WaitHandle.WaitAll(AutoResetEvents.ToArray());
}
/// <summary>
/// Waits for any of the elements in the specified array to receive a signal.
/// </summary>
/// <returns>The array index of the object that satisfied the wait.</returns>
public static int WaitAny()
{
return WaitHandle.WaitAny(AutoResetEvents.ToArray());
}
/// <summary>
/// A callback container with a WaitHandle.
/// </summary>
class WaitCallbackHelper
{
private WaitCallbackNew callback;
/// <summary>
///
/// </summary>
public WaitCallbackNew Callback
{
get
{
return this.callback;
}
set
{
this.callback = value;
}
}
private WaitHandle waitHandle;
public WaitHandle WaitHandle
{
get
{
return this.waitHandle;
}
set
{
this.waitHandle = value;
}
}
}
}
}
//ThreadPoolHelperExample.cs
using System;
using System.Threading;
namespace CA_ThreadPool
{
public class Example
{
public static void Main()
{
// Queue the task.
ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc));
ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc));
ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc));
//
//The code above will be predigested to the code down!
//
ThreadPoolHelper.QueueUserWorkItem(ThreadProc);
ThreadPoolHelper.QueueUserWorkItem(ThreadProc);
ThreadPoolHelper.QueueUserWorkItem(ThreadProc);
//
//The code above will be predigested to the code down!
//
ThreadPoolHelper.QueueUserWorkItems(ThreadProc, ThreadProc, ThreadProc);
//
//The code above will be evolved to the code down!
//
ThreadPoolHelper.QueueUserWorkItems(
delegate { Console.WriteLine("a"); },
delegate { Console.WriteLine("b"); },
delegate { Console.WriteLine("c"); }
);
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.)
//You can choose one of the code behind.
Thread.Sleep(1000); //You can set a overtime here.
ThreadPoolHelper.WaitAll(); //If you call it,the program will go on after all of your WorkItems to the end!
ThreadPoolHelper.WaitAny(); //If you call it,the program will go on after one of your WorkItems to the end!
Console.ReadKey();
//Console.WriteLine("Main thread exits.");
}
// This thread procedure performs the task.
static void ThreadProc()
{
// No state object was passed to QueueUserWorkItem, so
// stateInfo is null.
Console.WriteLine("Hello from the thread pool.()");
WasteTime2();
}
// 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.(object stateInfo)");
}
static void WasteTime1()
{
DateTime startTime = DateTime.Now;
object o = new object();
int i = 0;
int j = 0;
while (j < 1000000)
{
while (i < 1000000)
{
if (o is string)
{
o = new object();
}
else
{
o = new object();
}
i++;
}
j++;
}
DateTime endTime = DateTime.Now;
TimeSpan timeElapsed
= endTime - startTime;
Console.WriteLine("startTime:{0}"
, startTime.Millisecond);
Console.WriteLine("endTime:{0}"
, endTime.Millisecond);
Console.WriteLine("timeElapsed:{0}"
, timeElapsed.Milliseconds);
Console.WriteLine("________________");
}
static void WasteTime2()
{
DateTime startTime = DateTime.Now;
for (int i = 0; i < 1000000000; i++)
{
string s = "b" + "b" + "b" + "b"
+ "b" + "b" + "b" + "b"
+ "b" + "b" + "b" + "b"
+ "b" + "b" + "b" + "b"
+ "b" + "b" + "b" + "b"
+ "b" + "b" + "b" + "b"
+ "b" + "b" + "b" + "b"
+ "b" + "b" + "b" + "b";
}
DateTime endTime = DateTime.Now;
TimeSpan timeElapsed = endTime - startTime;
Console.WriteLine("startTime:{0}"
, startTime.Millisecond);
Console.WriteLine("endTime:{0}"
, endTime.Millisecond);
Console.WriteLine("timeElapsed:{0}"
, timeElapsed.Milliseconds);
Console.WriteLine("________________");
}
}
}