朱燚

--书到读透处,酒于微醺时

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  82 随笔 :: 21 文章 :: 517 评论 :: 46 引用

前几天遇到了一个问题,我在页面逻辑里需要调用一个webservice,处理一个比较耗时的操作,但是我不需要知道其返回值。于是我希望asp.net能像winform一样使用自动生成的webservice异步方法

你是不是想说:在页面调用webservice的时候,直接调用其异步实现不就完了吗?

这其实是行不通的,为了实现异步调用,我们需要对页面进行小小的改动,在Page元素里加上Async=true

我们很快就会发现这样做的问题:

让我们测试一下吧,现在我们在一个webservice的Helloworld方法中放入一个Thread。Sleep(10000),然后调用他的异步实现。通过调试,我们可以发现虽然程序运行至HelloworldAsync时,非常快速的返回并往下运行,但是当所有逻辑处理完成后,页面并不Response,而是硬生生等待我们的线程睡醒了才返回。

可是如果我希望真正做到调了不管怎么办呢? 

sync

你可以使用Thread,或者ThreadPool,自己来启动一个线程,我推荐使用ThreadPool,这样的话,这些线程都会被iis的线程池管理起来,不会造成崩溃

我们来分析一下这两种模式的运用有什么特点

WebService自带的异步模式为下图的模式

Async1

主线程调用子线程执行一个耗时操作(work1),同时执行一系列同步操作(w2...w5),然后交给w1返回

这种模式适合于work1有返回的情况,并且为了让work1得到充分的工作时间,异步调用的过程开始的越早越好,对web程序设计者而言,这里有一个很重要的问题:线程占用。。

刚才我们谈过,asp.net中每个请求都会有有一个线程来处理,而可以使用的线程是有限的,服务器会使用一个线程池来管理线程,当线程耗尽,ok,新来的请求只能蹲着排队,所以对web开发者而言,线程是个宝贵的资源,所以这个方案在并行处理的同时也增加了耗尽线程池的风险,毕竟一个请求造成了多个线程

 

用线程池来实现的模式属于下图

Async2

这种模式适合无返回的情况,这种情况下,对子线程的调用应该越晚越好,我们可以看到,主、子线程共存的时间越短,我们的稀缺资源线程就越安全,请注意的是,也许总的执行时间不会比同步的情况更少,但是我们很快就返回了用户界面,所以用户体验能够得到提高

使用web多线程的缺点 :

看了上面的叙述,你也许会说,那干脆把我所有的调用都改成异步调用吧,你尽管去做吧,绝对是一场灾难,因为在异步的同时,一定一会产生一个新的线程等待调用的返回,即使你调用函数的返回值为void,所以异步调用的负面效果将是会产生许多子线程,所以注意当你的调用非常耗时,这个子线程也将长期占用你的线程池,如果这样的调用大量出现,照样会消耗掉所有的可用线程

那么什么情况下适合在web上使用哪种多线程模式呢

我们来看看这段伪代码,他的用途是提交一个报告,方法传入一个报告,并从一个WebService中获得一些报告的内容,接着插入数据库,然后在文件服务器上生成一个报告文件,最后发出一个通知,让我们逐条命令的过一下这个方法,看看什么地方适合改为异步调用?(记得我们的讨论都是基于web的,关于桌面运用的多线程请参考 多线程总结一)

public void CreateReport(Report report){

//从webservice上取得报告的一些信息,不取得这些信息报告,报告是不完整的,是不能提交的

Report fullreport=CallWebService(report);

//插入数据库,很重要的工作

InsertIntoDataBase(fullreport)

try{

//生成报告文件,这里是一个耗时而且容易出错的操作

WriteStaticFile(fullreport)

}

catch{//记录错误日志。。。。}

//这个只是通知邮件

CallMailService2(fullreport)

}

第一条语句CallWebService()从一个webservice里加载一些报告的内容,这个是业务逻辑相关的,因为如果不加载的话报告内容是不完整的,不能提交,显然不能改为异步调了不管的模式,在这里你可以尝试模式一,但是这个改动是没有作用的,因为其他所有的过程,包括插入数据库,生成报告都依赖于这个方法的返回,所以如果我们在这里使用异步的话,其他的所有操作都必须等待他的返回,所以采用异步除了多增加了线程以外,一点时间也不能节省

再来看插入数据库,和上面一样也没有必要使用异步调用

生成报告这里比较有趣,确实他是一个和逻辑息息相关的操作,但是通过分析代码,我们可以看出,虽然报告生成是一个重要业务步骤,但是并没有严格到说"如果不能生成报告,就必须回滚上面的操作",并且如果操作失败,在catch中也仅仅是记录了日志,并没有需要尝试重写的逻辑,(很有可能另外的某个程序或者某人,会定时查看日志,发现有错误就重新生成文件)也就是说,就这段代码而言,生成也可以算一个额外逻辑,那么自然也可以去异步操作.可是:千万注意!!

由于生成报告需要的时间较长,那么生成报告的子线程会长时间运行,长期无法返回线程池,如果请求量太大,频率太快,那就会耗尽线程资源了.

平心而论,这个问题其实不是异步造成的,即使时同步调用,执行此操作也需要化肥很长时间,调用量太大,频率太快,也会造成排队.而且由于返回时间太长,用户体验也不会好,所以我们的这个改造应该是有益的

(注:关于报告生成,我在与一个同事讨论这种思想的时候,他就认为这个地方应该有一个写入队列,因为显然生成文件的速度和其他处理速度是不匹配的,这确实是一个比较合理的做法)

 

朱燚的技术博客,转载请注明出处

http://yizhu2000.cnblogs.com

http://blog.csdn.net/yizhu2000

 

posted on 2007-12-08 15:35 朱燚:-) 阅读(4383) 评论(22)  编辑 收藏 网摘

评论

#1楼 2007-12-08 16:17 sa1[未注册用户]
说了半天...
  回复  引用    

#2楼[楼主] 2007-12-08 16:25 二毛五      
就技术本身而言确实有点多余,主要篇幅是希望通过一些分析把在web中使用多线程的风险降低
  回复  引用  查看    

#3楼 2007-12-08 16:37 cnlamar      
也不上MSN,那玩意搞了嘛,你知道我是谁;p
  回复  引用  查看    

#4楼 2007-12-08 16:39 Stanley.Luo      
分析得很细致, 顶!
  回复  引用  查看    

#5楼 2007-12-08 17:27 黑羽飘舞      
二毛的文章很细致,我很喜欢,可惜此文实用性。。。。
  回复  引用  查看    

#6楼 2007-12-08 18:06 Jeffrey Zhao      
和用户体验关系不大,只是提高性能了——当然如果说性能提高了,用户体验就上去了我也没有意见,呵呵。
  回复  引用  查看    

#7楼 2007-12-08 19:27 Microshaoft      
在IIS的进程中玩多线程不是好办法
我一般是Web再远程调用到另一个Remoting Server Process,再实现异步多线程之类的
利用 Remoting 实现异步队列机制
http://www.cnblogs.com/Microshaoft/archive/2007/12/08/987944.html

  回复  引用  查看    

to Jeffrey Zhao:
实际上没有性能的提高,上面谈的方法,两个线程加起来的处理时间和单线程处理基本相同,只是返回的比较快,其实只是提高了用户体验

  回复  引用    

#9楼 2007-12-09 01:56 cnlamar      
2毛5,上MSN,给你留言了,有个好东西值得看看,看能否收到,收到收不到都给我发信息
  回复  引用  查看    

#10楼 2007-12-10 08:29 jun      
"......你尽管去做吧,绝对是一场灾难,因为在异步的同时,一定一会产生一个新的线程等待调用的返回......"

此句未必,如果用delegate的BeginInvoke加上callback,callback是在同一个异步的线程里执行的.

  回复  引用  查看    

#11楼 2007-12-10 08:57 ez.zhao      
同意jun的发言!麻烦博主先google下再发文!文章本身就有认识不清的意味!

  回复  引用  查看    

#12楼 2007-12-10 13:37 jecray      
Delegate.Invoke takes a thread from Thread Pool. When we use Delegate.Invoke on ASP.NET page, it takes a thread from ASP.NET thread pool. So, although the async page releases a thread into the pool, then that thread (or another one) is immediately taken by the Delegate.Invoke thread.

Now that thread is blocked until the ExecuteReader call finishes. So, when ExecuteReader takes a long time, a precious thread from ASP.NET Thread Pool is still occupied. So, we aren't releasing that thread back to thread pool by calling BeginExecuteReader.

So, in terms of timeline, here's what happens:

ASP.NET Takes a thread. Let's say Thread A.

Then it returns the Thread A to thread pool.

Delegate.BeginInvoke takes Thread X from thread pool in order to execute the LoadCategories function in background.

Thread X is blocked until LoadCategories function completes.

When Delegate invocation is complete, ASP.NET takes Thread B from thread pool and completes the page execution.

So, during this whole time, one thread was always occupied. In fact, it added an overhead of swtching from Thread A -> Thread X -> Thread B.

The only way we can truly have a thread returned to pool and made available to other thread requests are when SqlDataReader.BeginExecute is waiting on SQL Server to finish the work and during that moment the thread has been returned to Thread Pool.

  回复  引用  查看    

#13楼 2007-12-10 13:42 jecray      
--引用--------------------------------------------------
jun: "......你尽管去做吧,绝对是一场灾难,因为在异步的同时,一定一会产生一个新的线程等待调用的返回......"

此句未必,如果用delegate的BeginInvoke加上callback,callback是在同一个异步的线程里执行的.
--------------------------------------------------------
你好像没搞明白lz描述的意思.

  回复  引用  查看    

to:jecray ThreadB是指asp.net用于处理请求的thread吗?
  回复  引用    

#15楼 2007-12-10 14:35 jecray      
@yizhu2000
right.

There are two types of threads available to ASP.NET application. Worker thread and IO thread. When you call Delegate.Invoke, it eats up previous worker thread. But Stream.BeginXXX or Webservice.BeginXXX or BeginExecuteReader uses IO threads. Thus ASP.NET does not run out of worker threads.

So, as long as you use Delegate.Invoke, it will not make ASP.NET applications scalable. Instead it will add additional overhead. The principle here is to return worker threads to ASP.NET and wait on IO threads.

for more information, please see this article:
http://msdn.microsoft.com/msdnmag/issues/05/10/WickedCode/Default.aspx" target="_new">http://msdn.microsoft.com/msdnmag/issues/05/10/WickedCode/Default.aspx

  回复  引用  查看    

#16楼 2007-12-10 14:59 jun      
如你所说,在asp.net的场景下楼主也许是正确的.

--引用--------------------------------------------------
你好像没搞明白lz描述的意思.
--------------------------------------------------------

  回复  引用  查看    

#17楼 2007-12-13 20:34 酷勤经验      
lz有意思,叫二毛五
  回复  引用  查看    

#18楼 2007-12-14 08:59 Silent Void      
貌似WCF中有个One-Way的调用方式,调用儿而不用等待返回,不知道能不能实现楼主要的效果-,-
  回复  引用  查看    

#19楼[楼主] 2007-12-16 07:20 二毛五      
谢谢jecray的分享,
to silent
看来,我需要去看一看这个wcf的one-way了

  回复  引用  查看    

#20楼 2008-04-02 18:54 留恋星空      
MARK
  回复  引用  查看    

不错啊,收藏了
  回复  引用    




发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 982943




相关文章:

相关链接: