先看一段WebService中的离奇代码
//这是封装了WEBSERVICE的一个上层代码
public void AsyncPostURL(string url, string referer, string alt)
{
if (null == validateCode)
{
log.Error("AsyncPostURL:Authentication failed.");
}
else
{
if (!urlTable.ContainsKey(url))
{
if (urlTable.Count <= Properties.Settings.Default.UrlHashCount)
{
urlTable.Add(url, null);
}
else
{
//url地址获得量巨大,有成片相同的特性,所以满后清除
urlTable.Clear();
}
//opt是webservice的实例,PostURLCompleted是异步方法的事件处理函数
//AsyncPostURLCompleted是最终会调用回调函数
opt.PostURLCompleted += new PostURLCompletedEventHandler(AsyncPostURLCompleted);
opt.PostURLAsync(validateCode, url, referer, alt);
return;
}
else
{
log.Info("已包含的URL地址:" + url);
}
}
if (null != AsyncPostURLCallBack)
{
AsyncPostURLCallBack();//当异步处理结束,AsyncPostURLCompleted需要调用的方法
}
}
然后再看看AsyncPostURLCompleted的代码
private void AsyncPostURLCompleted(object sender, PostURLCompletedEventArgs e)
{
((Opt)sender).PostURLCompleted -= AsyncPostURLCompleted;//离奇吧,第一行居然是移除自身作为事件回调方法
if (null == e.Error)
{
log.Info("AsyncPostURLCompleted Result:" + e.Result);
}
else
{
log.Error("AsyncPostURLCompleted Result:" + e.Error.Message);
}
if (null != AsyncPostURLCallBack)
{
AsyncPostURLCallBack();
}
}
最后看看AsyncPostURLCallBack的代码,和调用 AsyncPostURL的代码:
asyncPostEmailDone = new ManualResetEvent(false);//调用代码需要建立一个单元信号
reporter.AsyncPostEmail(model.Url, model.Referer, model.Alt);
asyncPostEmailDone.WaitOne();//这里表示同时只能调用一个
private void AsyncPostURLProcess()
{
if (null != asyncPostURLDone)
{
asyncPostURLDone.Set();//重置,上面的代码可以再次运行
}
}
从这些代码中不难看出,WebService的异步处理,其实并非线程安全的,一个WebService对象里的所有异步方法,在同时只能一个方法运行。
而且,是在类似ThreadPool中,用QueueItem的方式运行。是会按照时刻不断的运行下去,好比ThreadPool.QueueUserWorkItem(AsyncPostURL);每次被调用后,移除事件处理,就是为了防止内部重复调用,而使用ManualResetEvent对象,却是为了在外部防止重复调用。不难看出,WebService的异步处理整个就是个麻烦。
ManualResetEvent对象只能作为多个线程之间的一个事件通知作用。要保证代码在多线程下同时只能被一个线程访问,还是需要Monitor(用于保证实例同一时间只被单个线程访问)。
Mutex.WaitOne和ReleaseMutex的组合能保证一段代码同时只被一个线程访问,Mutex还可以用命名的形式来完成进程间的同步。其实这些在Windows API中都有现成的模型。.Ner Framework只是封装了这些API。方式都没有变
泛型对象的同步,这里只用Queue<T>来说明一下
当对Queue使用两个线程分别作Enqueue何Dequeue操作时,一直没有碰到线程同步问题,搞的我以为Queue是同步的,后来,将两个线程都调用Enqueue方法,问题就来了,会有异常。可见Queue并非是一个同步对象,在其操作开始处使用Monitor.Enter,结束使用Monitor.Exit是很有必要的,或者使用Lock也行。
注意:在.Net FrameWork中,所有的异步处理都是使用ThreadPool中的线程。如果某个异步操作十分缓慢,那么线程耗尽是必然的。SO,,,这玩意还是给那些偶然性处理的事件比较好。不管是多线程,还是异步处理(DMA除外).都只有占有更多的CPU才能保证获得更多的处理时间,获得整体上的速度优势,一味的使用多线程并没有必要。只有当应用上需要时,才可以考虑使用多线程。反之,远远不如多开一个进程,这样不但数据有多个实例,方法也是。能够占用更多的CPU。使得针对于多线程同步问题而在代码中的额外开销得以避免。真要提高速度,还得在优化设计和算法上下功夫。