多线程编程的探索与实践(图)

来源:互联网 发布:91助手mac版官方下载 编辑:程序博客网 时间:2024/05/16 05:31

    在程序开发中,多线程是比较普遍的应用。还记得刚从事程序开发时,多线程对我来说是很神秘和充满向往的,如同雾里看花不知其要领和实质。由于工作中所参与的这个项目是基于多线程和多用户的,在经过一番历炼后对多线程有了一些拙见,特记录下来。
项目中没有直接使用开发环境所提供的多线程类库,而自己封装这些功能以提供更灵活便捷的编程方法。这里面基于一些概念:
    任务巢:多个任务的集合,提供任务包先进先出的队列管理。
    任务包:具体执行某个特定操作的任务,封装了要执行操作所需的若干参数、回调函数等。
    投递:打好任务包后,将任务包传递到任务巢排队执行。
    同步调用:调用及被调用者在同一个线程执行,在同一时刻只能执行其一。异步调用则反之。
    在看一下投递任务包及进行异步操作是怎样实现的:
    步骤: 创建任务包-->封装参数-->投递任务-->启动异步操作-->执行回调函数,发回执行结果。

    上述的机制可用下图来描述:

具体实现代码如下:
(代码段1)
IInvokePackage ip = (IInvokePackage)SendSysMsg(new CMsg(MTSystemService.CreateInvokePackage, false));  //创建任务包
if (null != ip) {
ip.TargetObject = this;
ip.TargetMethod = new InvokePackageHandler1(SureLoadProp);  //启动异步后要执行的操作
Debug.Assert(null != ip.TargetMethod);

ip.Parameters.Add(obj);  //封装参数
ip.Parameters.Add(strPropName);
ip.UserState = obj;
ip.Callback = new AsyncCallback(this.OnSureLoadPropCallback);  //回调函数
IAsyncResult ar = (IAsyncResult)SendSysMsg(new CMsg(MTSystemService.BeginInvoke, ip, this.m_PlugInMgr.DBJobNest));  //投递任务包,触发异步调用
    上述操作就是实现异步操作的整个过程,最后任务包对象ip将被投递到任务巢按先进先出的原则排队执行,最终将会在另一个线程中执行函数SureLoadProp,执行完毕,回调函数this.OnSureLoadPropCallback将会被调用:
(代码段2)
private void OnSureLoadPropCallback(IAsyncResult ar){
   ArrayList al = new ArrayList(1);
   object Result = SendSysMsg(new CMsg(MTSystemService.EndInvoke,ar,al));//请求执行结果

   IInvokePackage ip = (IInvokePackage)al[0];

   bool bRet = false;
   if(Result is Exception){
    AppGlobal.g.LogError((Exception)Result);
    bRet = false;
   }
   else{
    bRet = Result is ArrayList;
   }
   
   PostSysMsg(new CMsg(MTAppPatientMgrPlugIn.QueryPatientsResult, Result, ar, bRet));//发回操作结果
  }
    在上述函数中,首先通过消息向系统请求指定异步操作的执行结果,此时如果该任务还未执行完,此处将会等待直至执行完。然后通过消息QueryPatientsResult将执行结果Result对象返回给调用者。
    再回头看一下这里面的机制:
    代码段1的操作是在当前线程中(比如主线程)执行的,当任务包投递出去后就不用管了,当前线程可继续进行接下来的操作,直到接受任务包的任务巢(已经是另外一个线程)执行完毕通知回来再获取操作结果。这样做的好处就是,当要执行时间过长消耗性能较大的操作时,如果不用另外的线程来做,那么当前线程就要等待这个操作的完成,才能执行其他操作,这时就会出现某些持续的操作停顿,用户的操作也要受到影响。那么此时,将该任务交给另一个线程来做,当前线程的其他的操作不受影响,用户的操作也会很流畅。
当任务包投递出去后,并不能保证马上执行,因为接受此任务包的任务巢可能有多个任务包,它们是逐一执行的,刚投递的任务只有等待其他任务包执行完成后才能执行。虽然这样能确保在此任务巢中的任务能同步执行,但与其他任务巢(线程中的操作)不能同步,所以如果它们要访问同一资源时,就可能会出现读写迸发操作,造成数据不一致。这时可用锁定机制,这方面的应用在以前的文章中叙述过。
  

    那么如何将任务包投递到特定的任务巢呢,看代码1中的最后一句代码:
IAsyncResult ar = (IAsyncResult)SendSysMsg(new CMsg(MTSystemService.BeginInvoke, ip, this.m_PlugInMgr.DBJobNest));此时投递任务包是指定了任务巢this.m_PlugInMgr.DBJobNest,此任务巢是专门用作数据库操作的。如不指定,将会被投递到默认任务巢。
    总结:
    1. 在主线程中启动异步操作后,根据需要可在异步线程里面再次下一层异步操作。虽然异步操作增加程序执行的效率,但同时会增加程序的复杂度和管理难度。所以异步操作不是越多层次越好,在最大性能的前提下应尽量减少异步调度嵌套次数。
    2. 由于线程的调度几乎是在无声无息地进行,一旦出现多线程的问题将很难较直观地发现问题,此时必须要很清楚线程调度的思路,并在关键的地方输出如线程ID等信息,程序中隐晦的问题才会逐渐明朗,这是解决多线程问题的关键步骤。 

原创粉丝点击