使用Process类重定向时出现阻塞的解决方案


   来源: http://blog.csdn.net/temateroom/article/details/4871949 

使用Process类重定向时出现阻塞的解决方案

标签: outputwinformexceptioninput.net通讯
1892人阅读 评论(2) 收藏 举报
本文章已收录于:
分类:

 

【摘要】
    使用Process类重定向时遇到死锁问题,对Process的实现机制进行了一番思考,想看全文就点进去吧。

【全文】

[系统环境]
    .Net Framework 1.1,使用C#开发WinForm程序

[问题描述]
    程序中要调用外部程序cmd.exe执行一些命令行,并取得屏幕输出,使用了Process类,基本代码如下:


     Process  process  =  new  Process ();
     process . StartInfo . FileName  =  "cmd.exe" ;
     process . StartInfo . UseShellExecute  =  false ;
     process . StartInfo . RedirectStandardInput  =  true ;
     process . StartInfo . RedirectStandardOutput  =  true ;
     process . StartInfo . RedirectStandardError  =  true ;
     process . StartInfo . CreateNoWindow  =  true ;
     process . Start ();

    …………   //一些处理

     process . StandardInput . WriteLine ( "exit" );
     //取输出内容
     String  outputString  =  process . StandardOutput . ReadToEnd ();
     process . WaitForExit ();
     process . Close ();

    实际使用中发现有时程序会无响应,并且是在执行某些特定的命令时才会无响应。跟踪调试发现,问题出在process.StandardOutput.ReadToEnd()上,在这一行按F10,程序不向下执行,也不产生Exception。

[解决过程]
    由于调试时程序不向下执行,猜测ReadToEnd()没有返回,被阻塞住了,查了查MSDN,没有看到有相关内容。又反编了一下mscorlib.dll,看了看StreamReader.ReadToEnd()的实现方法,是这么一段代码:


     public  override  string  ReadToEnd ()
     {
         int  num1 ;
         if  ( this . stream  ==  null )
         {
             __Error . ReaderClosed ();
         }
         char []  chArray1  =  new  char [ this . charBuffer . Length ];
         StringBuilder  builder1  =  new  StringBuilder ( this . charBuffer . Length );
         while  (( num1  =  this . Read ( chArray1 ,  0 ,  chArray1 . Length ))  !=  0 )
         {
             builder1 . Append ( chArray1 ,  0 ,  num1 );
         }
         return  builder1 . ToString ();
     }

    从中看出ReadToEnd()是利用Read()方法实现的,又去MSDN看了,仍然没有提到Read()是阻塞函数,于是只能上网去查一查相关资料,有人提到了类似的问题,并且给出了解决方法:使用两个线程分别做StandardOutput.ReadToEnd()和StandardError.ReadToEnd()。试了一下,果然可以解决。

[后续思考]
    问题虽然解决,不过原理没有弄明白,还要继续深入。查阅相关资料,大致了解了Process的运作模式,它以子进程的形式启动cmd.exe,如果指定了重定向Input、Output或Error,就建立相应的管道在父子进程之间进行通讯。
    基于这些内容以及关于管道的理解,得到以下几条猜测:
    1、input、output、error的三条管道是相互独立的。
    2、管道有大小,空时不可读,满时不可写。
    3、遇到管道不可读写时,相应的进程会阻塞等待可读写为止。
    4、子进程结束前,output流与error流不会结束。

    这样再翻回来考虑之前遇到的问题,可以猜测无响应时是这样一种情况:
    建立Process时指定了Output和Error都重定向,父子进程间就建立了2条管道,cmd.exe不停输出output与error,分别进入两条管道,对output,管道满后,子进程会停下来等待父进程取走数据,父进程的StandardOutput.ReadToEnd()方法正是取数据的,它在管道空时会处于阻塞状态,有数据时就取走,这样子进程会继续写output。
    但是对于error,父进程没有相应的ReadToEnd()方法,很快error的管道就满了,由于无法写error,子进程会停下来等待,但是父进程永远不读,子进程也就永远不再继续,形成死锁。
    对于不出问题的情况,一定是error管道根本就没有写满,这也就解释了为什么命令的内容决定了是否会出现无响应的情况。

    那么直接在StandardOutput.ReadToEnd()后面加一条StandardError.ReadToEnd()行不行呢?答案是不行的,基于上面的猜测与分析,ReadToEnd()方法是阻塞的,在子进程结束前,output流不会关闭,所以StandardOutput.ReadToEnd()会一直等待,造成后面的StandardError.ReadToEnd()方法根本不被执行,还是会产生死锁。所以解决方案里才提到了要建立2个线程,让两个管道的ReadToEnd()方法独立执行。

    再回来考虑我的程序中的情况,之所以没有去读Error,是因为我根本不关心子进程产生的error输出,所以应该有更简单的方法,就是把process.StartInfo.RedirectStandardError置为false,经测试验证,想法是对的, 这样还避免了使用线程。
    在分析的过程中还对process.WaitForExit()方法产生了兴趣,从msdn的描述来看,它的作用是等待子进程结束,应该也是阻塞的,但在我的程序中它在ReadToEnd()方法后面,是否还有实际作用呢?猜测子进程结束后,ReadToEnd()方法才返回,WaitForExit()方法才被调用,但此时它已没有什么用了,肯定立刻返回,向下执行。跟踪调试了一下,验证了自己的想法,于是干脆去掉了这一句,经验证并没有对运行产生影响。

    以上很大部分都是自己的猜测,并不敢保证准确,也许很多地方理解有误,欢迎各位大牛指正。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值