liwei6797

 

用线程池(Thread Pool)实现异步执行程序

 原作者:Paul Kimmel

  开发比较大型的应用程序的时候,我们经常会遇到让应用显得很慢的大块处理过程。例如,你很可能需要在Windows程序开始的时候做大量的初始化处理,而这些处理不一定需要进行完,用户交互就可以进行了。
  在 VB.net 中,我们不再需要自行实现复杂的多线程处理来让我们的程序表现的活力十足。我们可以在 VB.net 中很容易地实现异步的多线程。
  在 .net 中,Visual Basic支持使用线程池(ThreadPool)中的进程。这种方法可以让我们的程序主体脱离那些漫长的处理过程,而不需要编写过多的额外线程管理代码。

使用线程池(ThreadPool)的好处 

  System.Threading.ThreadPool 类提供了一个虚拟的,包含可用线程的缓冲区。使用这个类可以轻易地编写异步多线程的程序,而不需要为此做类似构建新线程、编写线程管理器,或者做一个自己的线程池这样的复杂工作。 

  ThreadPool (线程池)是一个动态的集合,它包含了所有可以用于多线程处理的线程。线程池中的线程数是动态的,而且也不需要做管理线程池中 线程的额外工作。如果你在请求一个线程池中不存在的线程,那么线程池可以自动根据需要建立一个,或者等待现有的线程回到可用状态。一切都是在幕后完成的,并不需要程序员来关心。 

  这里我将通过一个虚拟的问题来示范一下如何使用线程池(ThreadPool)以及使用线程的好处。这个问题模拟一个需要时间来完成全部启动过程的Windows程序,具体来说,通过装载一千万个正数到一个列表框(ListBox)来模拟这个耗时的过程。如果这个应用程序简单地尝试整个列表框操作,这项工作可能需要几分钟的时间来完成,在此过程中,主窗体(main form)将不响应任何用户操作;但是,如果我们把加载数据的操作独立成一个单独的线程中,而让我们的主窗体(main form)自行加载,效果马上就会不同。(请注意,我们讨论的多线程的好处只有在你的窗体并不需要全部初始化完成即可使用的情况下才能体现出来)。 在这个虚拟的场景中,列表框(ListBox)的加载很快就让我再也坐不住了。通过使用多线程,主窗体(main form)立即呈现,而列表框(ListBox)则在背后继续完成它的加载,在这个过程中,ListBox的可见部分看上去是充满的。取代了漫长的启动过程,这个应用迅速地呈现了它的界面来接受用户的输入,甚至包括对列表框——它仍然再继续加载——的滚动。让我们来看一看这一切是如何通过ThreadPool类来完成的。 

使用线程池(ThreadPool)技术 

  通过添加WaitCallBack代理到线程池的队列中,我们要做的事情被添加进了线程池(ThreadPool)。当队列就绪时,它将用这个代理来完成任务。需要注意的是,尽管并不需要自行管理线程的运作,担当线程代理需要同窗体之间发生联系的时候,一定要格外小心。 

  等待回调代理[WaitCallBack delegate]是一个需要一个Object(对象)参数的代理子程序。下面是典型的等待回调[WaitCallBack]的定义。 

Public Delegate Sub WaitCallBack(ByVal State As Object) 

  包含所需处理工作的,是用单一Object参数的子程序的地址指针,可以作为参数传递给ThreadPool.QueueUserWorkItem方法。(ThreadPool.QueueUserWorkItem 是一个公共[shared]的方法。并不需要通过构建ThreadPool的实例来在线程池中添加项目)程序清单1示范了怎样把初始化过程独立为单独的线程。 

程序清单1: 使用ThreadPool中现有的线程来是线多线程。 

1: Private Sub Form1_Load(ByVal sender As System.Object, _
2:    ByVal e As System.EventArgs) Handles MyBase.Load
3: 
4:    ListBox1.Items.Clear()
5:    ThreadPool.QueueUserWorkItem(AddressOf Initialize)
6:
 7: End Sub
8: 
9: Private Elem As String
10: Private Sub Add()
11:   ListBox1.Items.Add(Elem)
12:   Application.DoEvents()
13: End Sub
14:
15: Private Sub Initialize(ByVal State As Object)
16:   Dim I As Integer
17:
18:   SyncLock ListBox1.GetType
19:
 20:     For I = 10000000 To 1 Step -1
21:       Elem = I
22:       ListBox1.Invoke(CType(AddressOf Add, MethodInvoker))
23:     Next
24:
25:   End SyncLock
26:
27: End Sub

  如果我们在窗体模块中完成列表的加载,那么在这一漫长过程完成以前,窗体的加载是不能完成的。第5行示范了ThreadPool是多么容易使用。通过把列表的初始化过程转移到一个单独的,符合等待回调[WaitCallBack]代理要求的子程序中来进行,我们就能够构建一个代理并且将它送到进程池的队列中去。等待回调子程序在15-27行定义。  

 
 

1: Threads(线程)窗口中显示的线程1300正在工作。 

  AddressOf Initialize 返回一个代理,这个代理将传递给 ThreadPool.QueueUserWorkItem 方法,而该方法将立即返回。ThreadPool将在线程池中一个单独的线程中使用Initialize方法来填充列表(参见图1)。由于Initialize方法是在一个单独的线程中,并且我们在同Windows窗体控件——ListBox1控件——打交道,因此我们还需要调用Invoke,象第22行所写的那样,来更安全地更新ListBox控件的内容。Invoke通过给窗体(和ListBox控件在同一线程中)的消息队列发送消息来同步我们所做的操作。总之,我们使用了一个代理,以及Invoke方法让在单独进程中的Initialize方法为ListBox添加项目。

  提示: 如果一个控件的处理程序和操作它的代码不在同一线程中,则需要使用 控件名.Invoke 方法(参看程序清单1)。如果你不清楚,可以调用 控件名.InvokeRequired 来判断一下你是否需要使用它(如果返回 True 则需要)。 

  最后是关于state参数:这个参数在上面的例子中是空(null)。也可以将AutoResetEvent对象作为第二参数传递给ThreadPool.QueueUserWorkItem的重载版本来管理进程池中的并发进程。 

总结 

  线程池是一条在 Visual Basic.net 应用中使用线程的捷径。线程池管理线程的构建和析构,并且监视线程对象的运行。Windows窗体控件[Form Controls]和线程共同工作并不是很安全的,因此,需要使用 Invoke 方法来在线程边界通知那些控件。 

  创建新的Thread对象和使用ThreadPool获得的性能相当。无论如何,在需要重量级的背景处理的时候,别忘了考虑使用ThreadPool的可能行。当然,你也同样可以使用TimerApplication.Idle事件来完成轻量级的异步处理,并且,如果愿意的话,自行建立线程也是可以的。

 MSDN中的示例:

using System;
using System.Threading;

public class TaskInfo
{
    
    
public string Boilerplate;
    
public int Value;
    
    
public TaskInfo(string text, int number)
    
{
        Boilerplate 
= text;
        Value 
= number;
    }

}


public class Example
{
    
public static void Main()
    
{
        TaskInfo ti 
= new TaskInfo("This report displays the number {0}."42);

        
// Queue the task and data.
        if (ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc), ti))
        
{
            Console.WriteLine(
"Main thread does some work, then sleeps.");            
            Thread.Sleep(
1000);
            Console.WriteLine(
"Main thread exits.");
        }

        
else
        
{
            Console.WriteLine(
"Unable to queue ThreadPool request.");
        }

    }


    
static void ThreadProc(Object stateInfo)
    
{
        TaskInfo ti 
= (TaskInfo)stateInfo;
        Console.WriteLine(ti.Boilerplate, ti.Value);
    }

}

 

 

posted on 2007-04-27 12:35  对倒二五条  阅读(569)  评论(0编辑  收藏  举报

导航