04.11.2014 Views

编程资料- 多线程 - 错误提示:发生了异常- 博客园

编程资料- 多线程 - 错误提示:发生了异常- 博客园

编程资料- 多线程 - 错误提示:发生了异常- 博客园

SHOW MORE
SHOW LESS

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

编 程 资 料 - 多 线 程<br />

C# 多 线 程 编 程 实 例 实 战<br />

作 者 : 刘 弹 www.ASPCool.com 时 间 :2003-5-17 上 午 10:24:05 阅 读 次 数 :10996<br />

单 个 写 入 程 序 / 多 个 阅 读 程 序 在 .Net 类 库 中 其 实 已 经 提 供 了 实 现 , 即<br />

System.Threading.ReaderWriterLock 类 。 本 文 通 过 对 常 见 的 单 个 写 入 / 多 个 阅 读 程 序 的 分 析 来 探 索 c#<br />

的 多 线 程 编 程 。<br />

问 题 的 提 出<br />

所 谓 单 个 写 入 程 序 / 多 个 阅 读 程 序 的 线 程 同 步 问 题 , 是 指 任 意 数 量 的 线 程 访 问 共 享 资 源 时 , 写 入 程 序<br />

( 线 程 ) 需 要 修 改 共 享 资 源 , 而 阅 读 程 序 ( 线 程 ) 需 要 读 取 数 据 。 在 这 个 同 步 问 题 中 , 很 容 易 得 到 下 面 二<br />

个 要 求 :<br />

1) 当 一 个 线 程 正 在 写 入 数 据 时 , 其 他 线 程 不 能 写 , 也 不 能 读 。<br />

2) 当 一 个 线 程 正 在 读 入 数 据 时 , 其 他 线 程 不 能 写 , 但 能 够 读 。<br />

在 数 据 库 应 用 程 序 环 境 中 经 常 遇 到 这 样 的 问 题 。 比 如 说 , 有 n 个 最 终 用 户 , 他 们 都 要 同 时 访 问 同 一<br />

个 数 据 库 。 其 中 有 m 个 用 户 要 将 数 据 存 入 数 据 库 ,n-m 个 用 户 要 读 取 数 据 库 中 的 记 录 。<br />

很 显 然 , 在 这 个 环 境 中 , 我 们 不 能 让 两 个 或 两 个 以 上 的 用 户 同 时 更 新 同 一 条 记 录 , 如 果 两 个 或 两 个<br />

以 上 的 用 户 都 试 图 同 时 修 改 同 一 记 录 , 那 么 该 记 录 中 的 信 息 就 会 被 破 坏 。<br />

我 们 也 不 让 一 个 用 户 更 新 数 据 库 记 录 的 同 时 , 让 另 一 用 户 读 取 记 录 的 内 容 。 因 为 读 取 的 记 录 很 有 可<br />

能 同 时 包 含 了 更 新 和 没 有 更 新 的 信 息 , 也 就 是 说 这 条 记 录 是 无 效 的 记 录 。<br />

实 现 分 析<br />

规 定 任 一 线 程 要 对 资 源 进 行 写 或 读 操 作 前 必 须 申 请 锁 。 根 据 操 作 的 不 同 , 分 为 阅 读 锁 和 写 入 锁 , 操<br />

作 完 成 之 后 应 释 放 相 应 的 锁 。 将 单 个 写 入 程 序 / 多 个 阅 读 程 序 的 要 求 改 变 一 下 , 可 以 得 到 如 下 的 形 式 :<br />

一 个 线 程 申 请 阅 读 锁 的 成 功 条 件 是 : 当 前 没 有 活 动 的 写 入 线 程 。<br />

一 个 线 程 申 请 写 入 锁 的 成 功 条 件 是 : 当 前 没 有 任 何 活 动 ( 对 锁 而 言 ) 的 线 程 。<br />

因 此 , 为 了 标 志 是 否 有 活 动 的 线 程 , 以 及 是 写 入 还 是 阅 读 线 程 , 引 入 一 个 变 量 m_nActive, 如 果<br />

m_nActive > 0, 则 表 示 当 前 活 动 阅 读 线 程 的 数 目 , 如 果 m_nActive=0, 则 表 示 没 有 任 何 活 动 线 程 ,m_nActive<br />


将 线 程 与 特 殊 标 志 位 关 联 起 来 。<br />

申 请 阅 读 锁 的 函 数 原 型 为 :public void AcquireReaderLock( int millisecondsTimeout ), 其 中<br />

的 参 数 为 线 程 等 待 调 度 的 时 间 。 函 数 定 义 如 下 :<br />

public void AcquireReaderLock( int millisecondsTimeout )<br />

{<br />

// m_mutext 很 快 可 以 得 到 , 以 便 进 入 临 界 区<br />

m_mutex.WaitOne( );<br />

// 是 否 有 写 入 线 程 存 在<br />

bool bExistingWriter = ( m_nActive < 0 );<br />

if( bExistingWriter )<br />

{ // 等 待 阅 读 线 程 数 目 加 1, 当 有 锁 释 放 时 , 根 据 此 数 目 来 调 度 线 程<br />

m_nWaitingReaders++;<br />

}<br />

else<br />

{ // 当 前 活 动 线 程 加 1<br />

m_nActive++;<br />

}<br />

m_mutex.ReleaseMutex();<br />

// 存 储 锁 标 志 为 Reader<br />

System.LocalDataStoreSlot slot = Thread.GetNamedDataSlot(m_strThreadSlotName);<br />

object obj = Thread.GetData( slot );<br />

LockFlags flag = LockFlags.None;<br />

if( obj != null )


flag = (LockFlags)obj ;<br />

if( flag == LockFlags.None )<br />

{<br />

Thread.SetData( slot, LockFlags.Reader );<br />

}<br />

else<br />

{<br />

Thread.SetData( slot, (LockFlags)((int)flag | (int)LockFlags.Reader ) );<br />

}<br />

if( bExistingWriter )<br />

{ // 等 待 指 定 的 时 间<br />

this.m_aeReaders.WaitOne( millisecondsTimeout, true );<br />

}<br />

}<br />

它 首 先 进 入 临 界 区 ( 用 以 在 多 线 程 环 境 下 保 证 活 动 线 程 数 目 的 操 作 的 正 确 性 ) 判 断 当 前 活 动 线 程 的<br />

数 目 , 如 果 有 写 线 程 (m_nActive=0), 则 可 以 让 读 线 程 继 续 运 行 。<br />

申 请 写 入 锁 的 函 数 原 型 为 :public void AcquireWriterLock( int millisecondsTimeout ), 其 中<br />

的 参 数 为 等 待 调 度 的 时 间 。 函 数 定 义 如 下 :<br />

public void AcquireWriterLock( int millisecondsTimeout )<br />

{<br />

// m_mutext 很 快 可 以 得 到 , 以 便 进 入 临 界 区<br />

m_mutex.WaitOne( );


是 否 有 活 动 线 程 存 在<br />

bool bNoActive = m_nActive == 0;<br />

if( !bNoActive )<br />

{<br />

m_nWaitingWriters++;<br />

}<br />

else<br />

{<br />

m_nActive--;<br />

}<br />

m_mutex.ReleaseMutex();<br />

// 存 储 线 程 锁 标 志<br />

System.LocalDataStoreSlot slot = Thread.GetNamedDataSlot( "myReaderWriterLockDataSlot" );<br />

object obj = Thread.GetData( slot );<br />

LockFlags flag = LockFlags.None;<br />

if( obj != null )<br />

flag = (LockFlags)Thread.GetData( slot );<br />

if( flag == LockFlags.None )<br />

{<br />

Thread.SetData( slot, LockFlags.Writer );<br />

}<br />

else


{<br />

Thread.SetData( slot, (LockFlags)((int)flag | (int)LockFlags.Writer ) );<br />

}<br />

// 如 果 有 活 动 线 程 , 等 待 指 定 的 时 间<br />

if( !bNoActive )<br />

this.m_aeWriters.WaitOne( millisecondsTimeout, true );<br />

}<br />

它 首 先 进 入 临 界 区 判 断 当 前 活 动 线 程 的 数 目 , 如 果 当 前 有 活 动 线 程 存 在 , 不 管 是 写 线 程 还 是 读 线 程<br />

(m_nActive), 线 程 将 等 待 指 定 的 时 间 并 且 等 待 的 写 入 线 程 数 目 加 1, 否 则 线 程 拥 有 写 的 权 限 。<br />

释 放 阅 读 锁 的 函 数 原 型 为 :public void ReleaseReaderLock()。 函 数 定 义 如 下 :<br />

public void ReleaseReaderLock()<br />

{<br />

System.LocalDataStoreSlot slot = Thread.GetNamedDataSlot(m_strThreadSlotName );<br />

LockFlags flag = (LockFlags)Thread.GetData( slot );<br />

if( flag == LockFlags.None )<br />

{<br />

return;<br />

}<br />

bool bReader = true;<br />

switch( flag )<br />

{<br />

case LockFlags.None:<br />

break;


case LockFlags.Writer:<br />

bReader = false;<br />

break;<br />

}<br />

if( !bReader )<br />

return;<br />

Thread.SetData( slot, LockFlags.None );<br />

m_mutex.WaitOne();<br />

AutoResetEvent autoresetevent = null;<br />

this.m_nActive --;<br />

if( this.m_nActive == 0 )<br />

{<br />

if( this.m_nWaitingReaders > 0 )<br />

{<br />

m_nActive ++ ;<br />

m_nWaitingReaders --;<br />

autoresetevent = this.m_aeReaders;<br />

}<br />

else if( this.m_nWaitingWriters > 0)<br />

{<br />

m_nWaitingWriters--;<br />

m_nActive --;


autoresetevent = this.m_aeWriters ;<br />

}<br />

}<br />

m_mutex.ReleaseMutex();<br />

if( autoresetevent != null )<br />

autoresetevent.Set();<br />

}<br />

释 放 阅 读 锁 时 , 首 先 判 断 当 前 线 程 是 否 拥 有 阅 读 锁 ( 通 过 线 程 局 部 存 储 的 标 志 ), 然 后 判 断 是 否 有<br />

等 待 的 阅 读 线 程 , 如 果 有 , 先 将 当 前 活 动 线 程 加 1, 等 待 阅 读 线 程 数 目 减 1, 然 后 置 事 件 为 有 信 号 。 如 果 没<br />

有 等 待 的 阅 读 线 程 , 判 断 是 否 有 等 待 的 写 入 线 程 , 如 果 有 则 活 动 线 程 数 目 减 1, 等 待 的 写 入 线 程 数 目 减 1。<br />

释 放 写 入 锁 与 释 放 阅 读 锁 的 过 程 基 本 一 致 , 可 以 参 看 源 代 码 。<br />

注 意 在 程 序 中 , 释 放 锁 时 , 只 会 唤 醒 一 个 阅 读 程 序 , 这 是 因 为 使 用 AutoResetEvent 的 原 历 , 读 者 可<br />

自 行 将 其 改 成 ManualResetEvent, 同 时 唤 醒 多 个 阅 读 程 序 , 此 时 应 令 m_nActive 等 于 整 个 等 待 的 阅 读 线 程<br />

数 目 。<br />

测 试<br />

测 试 程 序 取 自 .Net FrameSDK 中 的 一 个 例 子 , 只 是 稍 做 修 改 。 测 试 程 序 如 下 ,<br />

using System;<br />

using System.Threading;<br />

using MyThreading;<br />

class Resource {<br />

myReaderWriterLock rwl = new myReaderWriterLock();<br />

public void Read(Int32 threadNum) {<br />

rwl.AcquireReaderLock(Timeout.Infinite);<br />

try {


Console.WriteLine("Start Resource reading (Thread={0})", threadNum);<br />

Thread.Sleep(250);<br />

Console.WriteLine("Stop Resource reading (Thread={0})", threadNum);<br />

}<br />

finally {<br />

rwl.ReleaseReaderLock();<br />

}<br />

}<br />

public void Write(Int32 threadNum) {<br />

rwl.AcquireWriterLock(Timeout.Infinite);<br />

try {<br />

Console.WriteLine("Start Resource writing (Thread={0})", threadNum);<br />

Thread.Sleep(750);<br />

Console.WriteLine("Stop Resource writing (Thread={0})", threadNum);<br />

}<br />

finally {<br />

rwl.ReleaseWriterLock();<br />

}<br />

}<br />

}<br />

class App {<br />

static Int32 numAsyncOps = 20;


static AutoResetEvent asyncOpsAreDone = new AutoResetEvent(false);<br />

static Resource res = new Resource();<br />

public static void Main() {<br />

for (Int32 threadNum = 0; threadNum < 20; threadNum++) {<br />

ThreadPool.QueueUserWorkItem(new WaitCallback(UpdateResource), threadNum);<br />

}<br />

asyncOpsAreDone.WaitOne();<br />

Console.WriteLine("All operations have completed.");<br />

Console.ReadLine();<br />

}<br />

// The callback method's signature MUST match that of a System.Threading.TimerCallback<br />

// delegate (it takes an Object parameter and returns void)<br />

static void UpdateResource(Object state) {<br />

Int32 threadNum = (Int32) state;<br />

if ((threadNum % 2) != 0) res.Read(threadNum);<br />

else res.Write(threadNum);<br />

if (Interlocked.Decrement(ref numAsyncOps) == 0)<br />

asyncOpsAreDone.Set();<br />

}<br />

}<br />

从 测 试 结 果 中 可 以 看 出 , 可 以 满 足 单 个 写 入 程 序 \ 多 个 阅 读 程 序 的 实 现 要 求 。


C# 一 个 多 线 程 操 作 控 件 的 例 子 .<br />

// 多 线 程 间 控 件 的 操 作 例 子<br />

using System;<br />

using System.Collections.Generic;<br />

using System.ComponentModel;<br />

using System.Data;<br />

using System.Drawing;<br />

using System.Text;<br />

using System.Windows.Forms;<br />

using System.Threading;<br />

using System.Data.SqlClient;<br />

using System.Collections;<br />

namespace AutoMessager<br />

{<br />

delegate void myDelegate();<br />

delegate void SetTextCallback(string text);<br />

public partial class frmAutoMsg : Form<br />

{<br />

event myDelegate myEvent;<br />

string connStr = string.Empty;<br />

Thread thd;<br />

//private Icon eyeIcon;<br />

//private NotifyIconEx notifyIconA;<br />

//private NotifyIconEx notifyIconB;<br />

private bool canClosed = false;<br />

public frmAutoMsg()<br />

{<br />

this.ShowInTaskbar = false;<br />

InitializeComponent();<br />

//eyeIcon = new Icon(GetType(), "EYE.ICO");<br />

notifyIcon1.ContextMenu = contextMenuB;<br />

}<br />

private void SetText(string text)<br />

{<br />

// InvokeRequired required compares the thread ID of the<br />

// calling thread to the thread ID of the creating thread.<br />

// If these threads are different, it returns true.<br />

if (this.txtMsgStatus.InvokeRequired)<br />

{


}<br />

SetTextCallback d = new SetTextCallback(SetText);<br />

this.Invoke(d, new object[] { text });<br />

}<br />

else<br />

{<br />

this.txtMsgStatus.Text += text;<br />

}<br />

private void frmAutoMsg_Load(object sender, EventArgs e)<br />

{<br />

connStr = System.Configuration.ConfigurationManager.AppSe<br />

ttings["ConnString"];<br />

thd = new Thread(new ThreadStart(doEvent));<br />

thd.IsBackground = true;<br />

thd.Start();<br />

//doEvent();<br />

//notifyIcon1.Visible = true;<br />

}<br />

/// <br />

/// 员 工 合 同 到 期 提 醒<br />

/// <br />

void UpUserState()<br />

{<br />

SqlConnection conn = null;<br />

DateTime now = DateTime.Now;<br />

SqlTransaction tran = null;<br />

SqlDataReader dr = null;<br />

try<br />

{<br />

// 数 据 库 操 作 部 分 省 略<br />

s") + " ");<br />

SetText(" 系 统 提 示 : 职 员 合 同 消 息 更 新 成 功 ! ");<br />

SetText(" 执 行 时 间 :" + now.ToString("yyyy-MM-dd HH:mm:s<br />

}<br />

catch (Exception ex)<br />

{<br />

dr.Close();<br />

tran.Rollback();


SetText(" 系 统 错 误 : 职 员 合 同 消 息 更 新 错 误 :" + ex.Messa<br />

ge + " ");<br />

SetText(" 执 行 时 间 :" + now.ToString("yyyy-MM-dd HH:mm:<br />

ss") + " ");<br />

}<br />

finally<br />

{<br />

dr.Close();<br />

conn.Close();<br />

conn.Dispose();<br />

}<br />

}<br />

/// <br />

/// 采 购 及 供 货 到 货 提 醒<br />

/// <br />

void UpCaiGou()<br />

{<br />

SqlConnection conn = null;<br />

DateTime now = DateTime.Now;<br />

SqlTransaction tran = null;<br />

SqlDataReader dr = null;<br />

try<br />

{<br />

// 数 据 库 操 作 部 分 省 略<br />

ss") + " ");<br />

SetText(" 系 统 提 示 : 合 同 采 购 消 息 更 新 成 功 ! ");<br />

SetText(" 执 行 时 间 :" + now.ToString("yyyy-MM-dd HH:mm:<br />

}<br />

catch (Exception ex)<br />

{<br />

dr.Close();<br />

tran.Rollback();<br />

SetText(" 系 统 错 误 : 合 同 采 购 消 息 更 新 错 误 :" + ex.Messag<br />

e + " ");<br />

SetText(" 执 行 时 间 :" + now.ToString("yyyy-MM-dd HH:mm:<br />

ss") + " ");<br />

}<br />

finally<br />

{<br />

dr.Close();<br />

conn.Close();


}<br />

}<br />

conn.Dispose();<br />

/// <br />

/// 供 货 收 款 情 况 提 醒<br />

/// <br />

void GetMoney()<br />

{<br />

SqlConnection conn = null;<br />

DateTime now = DateTime.Now;<br />

try<br />

{<br />

// 数 据 库 操 作 部 分 省 略<br />

SetText(" 系 统 提 示 : 供 货 付 款 消 息 更 新 成 功 ! ");<br />

SetText(" 执 行 时 间 :" + now.ToString("yyyy-MM-dd HH:mm:s<br />

s") + " ");<br />

}<br />

catch (Exception ex)<br />

{<br />

SetText(" 系 统 错 误 : 供 货 付 款 消 息 更 新 错 误 :" + ex.Messag<br />

e + " ");<br />

SetText(" 执 行 时 间 :" + now.ToString("yyyy-MM-dd HH:mm:<br />

ss") + " ");<br />

}<br />

finally<br />

{<br />

conn.Close();<br />

conn.Dispose();<br />

}<br />

}<br />

void doEvent()<br />

{<br />

//int weather = int.Parse(weatherTime.Text);<br />

//int del = int.Parse(fileTime.Text);<br />

// if(weather < 1 || weather > 24 || del < 1 |<br />

| del > 24)<br />

// {<br />

// MessageBox.Show(" 时 间 输 入 有 错 !");<br />

// button1.Enabled = true;<br />

// return ;<br />

// }<br />

while (true)


{<br />

//DateTime now = DateTime.Now;<br />

int i = DateTime.Now.Hour;<br />

if (i > 2 && i < 4)<br />

{<br />

myEvent = new myDelegate(UpUserState);<br />

myEvent += new myDelegate(UpCaiGou);<br />

// myEvent += new myDelegate(GetMoney);<br />

}<br />

//if (now.Hour == 3)<br />

//{<br />

// myEventB = new myDelegate(deltemp);<br />

//}<br />

//if (myEventA != null) myEventA();<br />

//if (myEventB != null) myEventB();<br />

if (myEvent != null)<br />

{<br />

myEvent();<br />

myEvent = null;<br />

}<br />

Application.DoEvents();<br />

}<br />

}<br />

Thread.Sleep(6000000); // 每 100 分 钟 检 查 一 次 时 间<br />

private void frmAutoMsg_FormClosing(object sender, FormClosin<br />

gEventArgs e)<br />

{<br />

if (canClosed == false)<br />

{<br />

e.Cancel = true;<br />

this.Hide();<br />

this.Visible = false;<br />

//this.<br />

}<br />

}<br />

private void menuItem2_Click(object sender, EventArgs e)<br />

{<br />

this.ShowInTaskbar = true;<br />

this.Show();<br />

this.Visible = true; // 恢 复 主 窗 体


}<br />

private void menuItem1_Click(object sender, EventArgs e)<br />

{<br />

canClosed = true;<br />

Application.Exit();<br />

}<br />

private void notifyIcon1_MouseDoubleClick(object sender, Mous<br />

eEventArgs e)<br />

{<br />

this.ShowInTaskbar = true;<br />

this.Show();<br />

if (this.Visible == false)<br />

{<br />

this.Visible = true;<br />

}<br />

}<br />

private void btnClear_Click(object sender, EventArgs e)<br />

{<br />

this.txtMsgStatus.Text = "";<br />

}<br />

private void btnUpMsg_Click(object sender, EventArgs e)<br />

{<br />

myEvent = new myDelegate(UpUserState);<br />

myEvent += new myDelegate(UpCaiGou);<br />

//myEvent += new myDelegate(GetMoney);<br />

}<br />

}<br />

}<br />

if (myEvent != null)<br />

myEvent();


.NET 事 件 模 型 教 程 ( 一 )<br />

目 录<br />

• 事 件 、 事 件 处 理 程 序 概 念<br />

• 问 题 描 述 : 一 个 需 要 较 长 时 间 才 能 完 成 的 任 务<br />

• 高 耦 合 的 实 现<br />

• 事 件 模 型 的 解 决 方 案 , 简 单 易 懂 的 VB.NET 版 本<br />

• 委 托 (delegate) 简 介<br />

• C# 实 现<br />

• 向 “.NET Framework 类 库 设 计 指 南 ” 靠 拢 , 标 准 实 现<br />

事 件 、 事 件 处 理 程 序 概 念<br />

在 面 向 对 象 理 论 中 , 一 个 对 象 ( 类 的 实 例 ) 可 以 有 属 性 (property, 获 取 或 设 置 对 象 的 状 态 )、 方 法 (method,<br />

对 象 可 以 做 的 动 作 ) 等 成 员 外 , 还 有 事 件 (event)。 所 谓 事 件 , 是 对 象 内 部 状 态 发 生 了 某 些 变 化 、 或 者 对 象 做<br />

某 些 动 作 时 ( 或 做 之 前 、 做 之 后 ), 向 外 界 发 出 的 通 知 。 打 个 比 方 就 是 , 对 象 “ 张 三 ” 肚 子 疼 了 , 然 后 他 站 在 空 地<br />

上 大 叫 一 声 “ 我 肚 子 疼 了 !” 事 件 就 是 这 个 通 知 。<br />

那 么 , 相 对 于 对 象 内 部 发 出 的 事 件 通 知 , 外 部 环 境 可 能 需 要 应 对 某 些 事 件 的 发 生 , 而 做 出 相 应 的 反 应 。 接 着 上 面<br />

的 比 方 , 张 三 大 叫 一 声 之 后 , 救 护 车 来 了 把 它 接 到 医 院 ( 或 者 疯 人 院 , 呵 呵 , 开 个 玩 笑 )。 外 界 因 应 事 件 发 生 而<br />

做 出 的 反 应 ( 具 体 到 程 序 上 , 就 是 针 对 该 事 件 而 写 的 那 些 处 理 代 码 ), 称 为 事 件 处 理 程 序 (event handler)。<br />

事 件 处 理 程 序 必 须 和 对 象 的 事 件 挂 钩 后 , 才 可 能 会 被 执 行 。 否 则 , 孤 立 的 事 件 处 理 程 序 不 会 被 执 行 。 另 一 方 面 ,<br />

对 象 发 生 事 件 时 , 并 不 一 定 要 有 相 应 的 处 理 程 序 。 就 如 张 三 大 叫 之 后 , 外 界 环 境 没 有 做 出 任 何 反 应 。 也 就 是 说 ,<br />

对 象 的 事 件 和 外 界 对 该 对 象 的 事 件 处 理 之 间 , 并 没 有 必 然 的 联 系 , 需 要 你 去 挂 接 。<br />

在 开 始 学 习 之 前 , 我 希 望 大 家 首 先 区 分 “ 事 件 ” 和 “ 事 件 处 理 程 序 ” 这 两 个 概 念 。 事 件 是 隶 属 于 对 象 ( 类 ) 本 身 的 ,<br />

事 件 处 理 程 序 是 外 界 代 码 针 对 对 象 的 事 件 做 出 的 反 应 。 事 件 , 是 对 象 ( 类 ) 的 设 计 者 、 开 发 者 应 该 完 成 的 ; 事 件<br />

处 理 程 序 是 外 界 调 用 方 需 要 完 成 的 。 简 单 的 说 , 事 件 是 “ 内 ”; 事 件 处 理 程 序 是 “ 外 ”。<br />

了 解 以 上 基 本 概 念 之 后 , 我 们 开 始 学 习 具 体 的 代 码 实 现 过 程 。 因 为 涉 及 代 码 比 较 多 , 限 于 篇 幅 , 我 只 是 将 代 码 中<br />

比 较 重 要 的 部 分 贴 在 文 章 里 , 进 行 解 析 , 剩 余 代 码 还 是 请 读 者 自 己 查 阅 , 我 已 经 把 源 代 码 打 了 包 提 供 下 载 。 我 也<br />

建 议 你 对 照 这 些 源 代 码 , 来 学 习 教 程 。[ 下 载 本 教 程 的 源 代 码 ]<br />

[TOP]<br />

问 题 描 述 : 一 个 需 要 较 长 时 间 才 能 完 成 的 任 务<br />

Demo 1A, 问 题 描 述 。 这 是 一 个 情 景 演 示 , 也 是 本 教 程 中 其 他 Demo 都 致 力 于 解 决 的 一 个 “ 实 际 问 题 ”:Worker<br />

类 中 有 一 个 可 能 需 要 较 长 时 间 才 能 完 成 的 方 法 DoLongTimeTask:<br />

using System;<br />

using System.Threading;


namespace percyboy.EventModelDemo.Demo1A<br />

{<br />

// 需 要 做 很 长 时 间 才 能 完 成 任 务 的 Worker, 没 有 加 入 任 何 汇 报 途 径 。<br />

public class Worker<br />

{<br />

// 请 根 据 你 的 机 器 配 置 情 况 , 设 置 MAX 的 值 。<br />

// 在 我 这 里 (CPU: AMD Sempron 2400+, DDRAM 512MB)<br />

// 当 MAX = 10000, 任 务 耗 时 20 秒 。<br />

private const int MAX = 10000;<br />

于 此 。<br />

常 运 行 。<br />

一 点 。<br />

public Worker()<br />

{<br />

}<br />

public void DoLongTimeTask()<br />

{<br />

int i;<br />

bool t = false;<br />

for (i = 0; i


单 击 “Start” 按 钮 后 , 开 始 执 行 该 方 法 。( 具 体 的 机 器 配 置 条 件 , 完 成 此 任 务 需 要 的 时 间 也 不 同 , 你 可 以 根 据 你<br />

的 实 际 情 况 调 整 代 码 中 的 MAX 值 。)<br />

在 没 有 进 度 指 示 的 情 况 下 , 界 面 长 时 间 的 无 响 应 , 往 往 会 被 用 户 认 为 是 程 序 故 障 或 者 “ 死 机 ”, 而 实 际 上 , 你 的 工<br />

作 正 在 进 行 还 没 有 结 束 。 此 次 教 程 就 是 以 解 决 此 问 题 为 实 例 , 向 你 介 绍 .NET 中 事 件 模 型 的 原 理 、 设 计 与 具 体<br />

编 码 实 现 。<br />

[TOP]<br />

高 耦 合 的 实 现<br />

Demo 1B, 高 度 耦 合 。 有 很 多 办 法 可 以 让 Worker 在 工 作 的 时 候 向 用 户 界 面 报 告 进 度 , 比 如 最 容 易 想 到 的 :<br />

public void DoLongTimeTask()<br />

{<br />

int i;<br />

bool t = false;<br />

for (i = 0; i


ate);<br />

double rate = (double)i / (double)MAX;<br />

this.statusbar.Text = String.Format(@" 已 完 成 {0:P2} ...",<br />

不 过 这 样 的 话 ,DoLongTimeTask 就 是 这 个 Windows 窗 体 的 一 部 分 了 , 显 然 它 不 利 于 其 他 窗 体 调 用 这 段 代<br />

码 。 那 么 :Worker 类 应 该 作 为 一 个 相 对 独 立 的 部 分 存 在 。 源 代 码 Demo1B 中 给 出 了 这 样 的 一 个 示 例 ( 应 该<br />

还 有 很 多 种 、 和 它 类 似 的 方 法 ):<br />

Windows 窗 体 Form1 中 单 击 “Start” 按 钮 后 , 初 始 化 Worker 类 的 一 个 新 实 例 , 并 执 行 它 的<br />

DoLongTimeTask 方 法 。 但 你 应 该 同 时 看 到 ,Form1 也 赋 值 给 Worker 的 一 个 属 性 , 在 Worker 执 行<br />

DoLongTimeTask 方 法 时 , 通 过 这 个 属 性 刷 新 Form1 的 状 态 栏 。Form1 和 Worker 之 间 相 互 粘 在 一 起 :<br />

Form1 依 赖 于 Worker 类 ( 因 为 它 单 击 按 钮 后 要 实 例 化 Worker),Worker 类 也 依 赖 于 Form1( 因 为 它 在<br />

工 作 时 , 需 要 访 问 Form1)。 这 二 者 之 间 形 成 了 高 度 耦 合 。<br />

高 度 耦 合 同 样 不 利 于 代 码 重 用 , 你 仍 然 无 法 在 另 一 个 窗 体 里 使 用 Worker 类 , 代 码 灵 活 度 大 为 降 低 。 正 确 的 设<br />

计 原 则 应 该 是 努 力 实 现 低 耦 合 : 如 果 Form1 必 须 依 赖 于 Worker 类 , 那 么 Worker 类 就 不 应 该 再 反 过 来 依<br />

赖 于 Form1。<br />

下 面 我 们 考 虑 使 用 .NET 事 件 模 型 解 决 上 述 的 “ 高 度 耦 合 ” 问 题 :<br />

让 Worker 类 在 工 作 时 , 向 外 界 发 出 “ 进 度 报 告 ” 的 事 件 通 知 (RateReport)。 同 时 , 为 了 演 示 更 多 的 情 景 ,<br />

我 们 让 Worker 类 在 开 始 DoLongTimeTask 之 前 发 出 一 个 “ 我 要 开 始 干 活 了 ! 总 任 务 数 有 N 件 。” 的 事 件 通<br />

知 (StartWork), 并 在 完 成 任 务 时 发 出 “ 任 务 完 成 ” 的 事 件 通 知 (EndWork)。<br />

采 用 事 件 模 型 后 , 类 Worker 本 身 并 不 实 际 去 刷 新 Form1 的 状 态 栏 , 也 就 是 说 Worker 不 依 赖 于 Form1。<br />

在 Form1 中 , 单 击 “Start” 按 钮 后 ,Worker 的 一 个 实 例 开 始 工 作 , 并 发 出 一 系 列 的 事 件 通 知 。 我 们 需 要 做 的<br />

是 为 Worker 的 事 件 书 写 事 件 处 理 程 序 , 并 将 它 们 挂 接 起 来 。<br />

[TOP]<br />

事 件 模 型 的 解 决 方 案 , 简 单 易 懂 的 VB.NET 版 本<br />

Demo 1C,VB.NET 代 码 。 虽 然 本 教 程 以 C# 为 示 例 语 言 , 我 还 是 给 出 一 段 VB.NET 的 代 码 辅 助 大 家 的 理<br />

解 。 因 为 我 个 人 认 为 VB.NET 的 事 件 语 法 , 能 让 你 非 常 直 观 的 领 悟 到 .NET 事 件 模 型 的 “ 思 维 方 式 ”:<br />

Public Class Worker<br />

Private Const MAX = 10000<br />

Public Sub New()<br />

End Sub


' 注 : 此 例 的 写 法 不 符 合 .NET Framework 类 库 设 计 指 南 中 的 约 定 ,<br />

' 只 是 为 了 让 你 快 速 理 解 事 件 模 型 而 简 化 的 。<br />

' 请 继 续 阅 读 , 使 用 Demo 1F 的 VB.NET 标 准 写 法 。<br />

'<br />

' 工 作 开 始 事 件 , 并 同 时 通 知 外 界 需 要 完 成 的 数 量 。<br />

Public Event StartWork(ByVal totalUnits As Integer)<br />

' 进 度 汇 报 事 件 , 通 知 外 界 任 务 完 成 的 进 度 情 况 。<br />

Public Event RateReport(ByVal rate As Double)<br />

' 工 作 结 束 事 件 。<br />

Public Event EndWork()<br />

Public Sub DoLongTimeTask()<br />

Dim i As Integer<br />

Dim t As Boolean = False<br />

Dim rate As Double<br />

End Sub<br />

' 开 始 工 作 前 , 向 外 界 发 出 事 件 通 知<br />

RaiseEvent StartWork(MAX)<br />

For i = 0 To MAX<br />

Thread.Sleep(1)<br />

t = Not t<br />

rate = i / MAX<br />

RaiseEvent RateReport(rate)<br />

Next<br />

RaiseEvent EndWork()<br />

首 先 是 事 件 的 声 明 部 分 : 你 只 需 写 上 Public Event 关 键 字 , 然 后 写 事 件 的 名 称 , 后 面 的 参 数 部 分 写 上 需 要 发 送<br />

到 外 界 的 参 数 声 明 。<br />

然 后 请 注 意 已 标 记 为 蓝 色 的 RaiseEvent 关 键 字 ,VB.NET 使 用 此 关 键 字 在 类 内 部 引 发 事 件 , 也 就 是 向 外 界 发<br />

送 事 件 通 知 。 请 注 意 它 的 语 法 ,RaiseEvent 后 接 上 你 要 引 发 的 事 件 名 称 , 然 后 是 具 体 的 事 件 参 数 值 。<br />

从 这 个 例 子 中 , 我 们 可 以 加 深 对 事 件 模 型 的 认 识 : 事 件 是 对 象 ( 类 ) 的 成 员 , 在 对 象 ( 类 ) 内 部 状 态 发 生 了 一 些<br />

变 化 ( 比 如 此 例 中 rate 在 变 化 ), 或 者 对 象 做 一 些 动 作 时 ( 比 如 此 例 中 , 方 法 开 始 时 , 向 外 界 raise event;<br />

方 法 结 束 时 , 向 外 界 raise event), 对 象 ( 类 ) 发 出 的 通 知 。 并 且 , 你 也 了 解 了 事 件 参 数 的 用 法 : 事 件 参 数 是<br />

事 件 通 知 的 相 关 内 容 , 比 如 RateReport 事 件 通 知 需 要 报 告 进 度 值 rate,StartWork 事 件 通 知 需 要 报 告 总 任<br />

务 数 MAX。


我 想 RaiseEvent 很 形 象 的 说 明 了 这 些 道 理 。<br />

[TOP]<br />

委 托 (delegate) 简 介 。<br />

在 学 习 C# 实 现 之 前 , 我 们 首 先 应 该 了 解 一 些 关 于 “ 委 托 ” 的 基 础 概 念 。<br />

你 可 以 简 单 的 把 “ 委 托 (delegate)” 理 解 为 .NET 对 函 数 的 包 装 ( 这 是 委 托 的 主 要 用 途 )。 委 托 代 表 一 “ 类 ” 函<br />

数 , 它 们 都 符 合 一 定 的 规 格 , 如 : 拥 有 相 同 的 参 数 个 数 、 参 数 类 型 、 返 回 值 类 型 等 。 也 可 以 认 为 委 托 是 对 函 数 的<br />

抽 象 , 是 函 数 的 “ 类 ”( 类 是 具 有 某 些 相 同 特 征 的 事 物 的 抽 象 )。 这 时 , 委 托 的 实 例 将 代 表 一 个 具 体 的 函 数 。<br />

你 可 以 用 如 下 的 方 式 声 明 委 托 :<br />

public delegate void MyDelegate(int integerParameter);<br />

如 上 的 委 托 将 可 以 用 于 代 表 : 有 且 只 有 一 个 整 数 型 参 数 、 且 不 带 返 回 值 的 一 组 函 数 。 它 的 写 法 和 一 个 函 数 的 写 法<br />

类 似 , 只 是 多 了 delegate 关 键 字 、 而 没 有 函 数 体 。( 注 : 本 文 中 的 函 数 (function), 取 了 面 向 过 程 理 论 中 惯<br />

用 的 术 语 。 在 完 全 面 向 对 象 的 .NET/C# 中 , 我 用 以 指 代 类 的 实 例 方 法 或 静 态 方 法 (method), 希 望 不 会 因 此<br />

引 起 误 解 。 顺 带 地 , 既 然 完 全 面 向 对 象 , 其 实 委 托 本 身 也 是 一 种 对 象 。)<br />

委 托 的 实 例 化 : 既 然 委 托 是 函 数 的 “ 类 ”, 那 么 使 用 委 托 之 前 也 需 要 实 例 化 。 我 们 先 看 如 下 的 代 码 :<br />

public class Sample<br />

{<br />

public void DoSomething(int mode)<br />

{<br />

Console.WriteLine("test function.");<br />

}<br />

}<br />

public static void Hello(int world)<br />

{<br />

Console.WriteLine("hello, world!");<br />

}<br />

我 们 看 到 Sample 的 实 例 方 法 DoSomething 和 静 态 方 法 Hello 都 符 合 上 面 已 经 定 义 了 的 MyDelegate<br />

委 托 的 “ 规 格 ”。 那 么 我 们 可 以 使 用 MyDelegate 委 托 来 包 装 它 们 , 以 用 于 特 殊 的 用 途 ( 比 如 下 面 要 讲 的 事 件 模<br />

型 , 或 者 将 来 教 程 中 要 讲 的 多 线 程 模 型 )。 当 然 , 包 装 的 过 程 其 实 也 是 委 托 的 实 例 化 过 程 :<br />

Sample sp = new Sample();<br />

MyDelegate del = new MyDelegate(sp.DoSomething);


这 是 对 上 面 的 实 例 方 法 的 包 装 。 但 如 果 这 段 代 码 写 在 Sample 类 内 部 , 则 应 使 用 this.DoSomething 而 不 用<br />

新 建 一 个 Sample 实 例 。 对 Sample 的 Hello 静 态 方 法 可 以 包 装 如 下 :<br />

MyDelegate del = new MyDelegate(Sample.Hello);<br />

调 用 委 托 : 对 于 某 个 委 托 的 实 例 ( 其 实 是 一 个 具 体 的 函 数 ), 如 果 想 执 行 它 :<br />

del(12345);<br />

直 接 写 上 委 托 实 例 的 名 字 , 并 在 括 号 中 给 相 应 的 参 数 赋 值 即 可 。( 如 果 函 数 有 返 回 值 , 也 可 以 像 普 通 函 数 那 样 接<br />

收 返 回 值 )。<br />

[TOP]<br />

C# 实 现<br />

Demo 1D,C# 实 现 。 这 里 给 出 Demo 1C 中 VB.NET 代 码 的 C# 实 现 : 是 不 是 比 VB.NET 的 代 码 复 杂<br />

了 一 些 呢 ?<br />

using System;<br />

using System.Threading;<br />

namespace percyboy.EventModelDemo.Demo1D<br />

{<br />

// 需 要 做 很 长 时 间 才 能 完 成 任 务 的 Worker, 这 次 我 们 使 用 事 件 向 外 界 通 知<br />

进 度 。<br />

public class Worker<br />

{<br />

private const int MAX = 10000;<br />

// 注 : 此 例 的 写 法 不 符 合 .NET Framework 类 库 设 计 指 南 中 的 约 定 ,<br />

// 只 是 为 了 让 你 快 速 理 解 事 件 模 型 而 简 化 的 。<br />

// 请 继 续 阅 读 , 使 用 Demo 1E / Demo 1H 的 C# 标 准 写 法 。<br />

//<br />

public delegate void StartWorkEventHandler(int totalUnits);<br />

public delegate void EndWorkEventHandler();<br />

public delegate void RateReportEventHandler(double rate);<br />

public event StartWorkEventHandler StartWork;<br />

public event EndWorkEventHandler EndWork;<br />

public event RateReportEventHandler RateReport;


public Worker()<br />

{<br />

}<br />

public void DoLongTimeTask()<br />

{<br />

int i;<br />

bool t = false;<br />

double rate;<br />

if (StartWork != null)<br />

{<br />

StartWork(MAX);<br />

}<br />

for (i = 0; i


if (RateReport != null)<br />

{<br />

RateReport(rate);<br />

}<br />

在 调 用 委 托 之 前 , 必 须 检 查 委 托 是 否 为 null, 否 则 将 有 可 能 引 发 NullReferenceException 意 外 ; 比 较 VB.NET<br />

的 代 码 ,VB.NET 的 RaiseEvent 语 句 实 际 上 也 隐 藏 了 这 一 细 节 。<br />

好 了 , 到 此 为 止 ,Worker 类 部 分 通 过 事 件 模 型 向 外 界 发 送 事 件 通 知 的 功 能 已 经 有 了 第 一 个 版 本 , 修 改 你 的<br />

Windows 窗 体 , 给 它 添 加 RateReport 事 件 处 理 程 序 ( 请 参 看 你 已 下 载 的 源 代 码 ), 并 挂 接 到 一 起 , 看 看 现<br />

在 的 效 果 :<br />

添 加 了 进 度 指 示 之 后 的 界 面 , 极 大 的 改 善 了 用 户 体 验 , 对 用 户 更 为 友 好 。<br />

[TOP]<br />

向 “.NET Framework 类 库 设 计 指 南 ” 靠 拢 , 标 准 实 现<br />

Demo 1E,C# 的 标 准 实 现 。 上 文 已 经 反 复 强 调 了 Demo 1C, Demo 1D 代 码 不 符 合 CLS 约 定 。 微 软<br />

为 .NET 类 库 的 设 计 与 命 名 提 出 了 一 些 指 南 , 作 为 一 种 约 定 ,.NET 开 发 者 应 当 遵 守 这 些 约 定 。 涉 及 事 件 的 部 分 ,<br />

请 参 看 事 件 命 名 指 南 ( 对 应 的 在 线 网 页 ), 事 件 使 用 指 南 ( 对 应 的 在 线 网 页 )。<br />

using System;<br />

using System.Threading;<br />

namespace percyboy.EventModelDemo.Demo1E<br />

{<br />

public class Worker<br />

{<br />

private const int MAX = 10000;


public class StartWorkEventArgs : EventArgs<br />

{<br />

private int totalUnits;<br />

public int TotalUnits<br />

{<br />

get { return totalUnits; }<br />

}<br />

}<br />

public StartWorkEventArgs(int totalUnits)<br />

{<br />

this.totalUnits = totalUnits;<br />

}<br />

public class RateReportEventArgs : EventArgs<br />

{<br />

private double rate;<br />

public double Rate<br />

{<br />

get { return rate; }<br />

}<br />

}<br />

public RateReportEventArgs(double rate)<br />

{<br />

this.rate = rate;<br />

}<br />

public delegate void StartWorkEventHandler(object sender,<br />

StartWorkEventArgs e);<br />

public delegate void RateReportEventHandler(object sender,<br />

RateReportEventArgs e);<br />

public event StartWorkEventHandler StartWork;<br />

public event EventHandler EndWork;<br />

public event RateReportEventHandler RateReport;<br />

protected virtual void OnStartWork( StartWorkEventArgs e )<br />

{<br />

if (StartWork != null)<br />

{


}<br />

}<br />

StartWork(this, e);<br />

protected virtual void OnEndWork( EventArgs e )<br />

{<br />

if (EndWork != null)<br />

{<br />

EndWork(this, e);<br />

}<br />

}<br />

protected virtual void OnRateReport( RateReportEventArgs e )<br />

{<br />

if (RateReport != null)<br />

{<br />

RateReport(this, e);<br />

}<br />

}<br />

public Worker()<br />

{<br />

}<br />

public void DoLongTimeTask()<br />

{<br />

int i;<br />

bool t = false;<br />

double rate;<br />

OnStartWork(new StartWorkEventArgs(MAX) );<br />

for (i = 0; i


}<br />

按 照 .NET Framework 类 库 设 计 指 南 中 的 约 定 :<br />

(1) 事 件 委 托 名 称 应 以 EventHandler 为 结 尾 ;<br />

(2) 事 件 委 托 的 “ 规 格 ” 应 该 是 两 个 参 数 : 第 一 个 参 数 是 object 类 型 的 sender, 代 表 发 出 事 件 通 知 的 对 象 ( 代<br />

码 中 一 般 是 this 关 键 字 (VB.NET 中 是 Me))。 第 二 个 参 数 e, 应 该 是 EventArgs 类 型 或 者 从 EventArgs 继<br />

承 而 来 的 类 型 ;<br />

事 件 参 数 类 型 , 应 从 EventArgs 继 承 , 名 称 应 以 EventArgs 结 尾 。 应 该 将 所 有 想 通 过 事 件 、 传 达 到 外 界 的 信<br />

息 , 放 在 事 件 参 数 e 中 。<br />

(3) 一 般 的 , 只 要 类 不 是 密 封 (C# 中 的 sealed,VB.NET 中 的 NotInheritable) 的 , 或 者 说 此 类 可 被 继 承 ,<br />

应 该 为 每 个 事 件 提 供 一 个 protected 并 且 是 可 重 写 (C# 用 virtual,VB.NET 用 Overridable) 的 OnXxxx<br />

方 法 : 该 方 法 名 称 , 应 该 是 On 加 上 事 件 的 名 称 ; 只 有 一 个 事 件 参 数 e; 一 般 在 该 方 法 中 进 行 null 判 断 , 并 且<br />

把 this/Me 作 为 sender 执 行 事 件 委 托 ; 在 需 要 发 出 事 件 通 知 的 地 方 , 应 调 用 此 OnXxxx 方 法 。<br />

对 于 此 类 的 子 类 , 如 果 要 改 变 发 生 此 事 件 时 的 行 为 , 应 重 写 OnXxxx 方 法 ; 并 且 在 重 写 时 , 一 般 情 况 下 应 调 用<br />

基 类 的 此 方 法 (C# 里 的 base.OnXxxx,VB.NET 用 MyBase.OnXxxx)。<br />

我 建 议 你 能 继 续 花 些 时 间 研 究 一 下 这 份 代 码 的 写 法 , 它 是 C# 的 标 准 事 件 实 现 代 码 , 相 信 你 会 用 得 着 它 !<br />

在 Demo 1D 中 我 没 有 讲 解 如 何 将 事 件 处 理 程 序 挂 接 到 Worker 实 例 的 事 件 的 代 码 , 在 这 个 Demo 中 , 我 将<br />

主 要 的 部 分 列 在 这 里 :<br />

private void button1_Click(object sender, System.EventArgs e)<br />

{<br />

statusBar1.Text = " 开 始 工 作 ....";<br />

this.Cursor = Cursors.WaitCursor;<br />

long tick = DateTime.Now.Ticks;<br />

Worker worker = new Worker();<br />

// 将 事 件 处 理 程 序 与 Worker 的 相 应 事 件 挂 钩<br />

// 这 里 我 只 挂 钩 了 RateReport 事 件 做 示 意<br />

worker.RateReport += new<br />

Worker.RateReportEventHandler(this.worker_RateReport);<br />

worker.DoLongTimeTask();<br />

tick = DateTime.Now.Ticks - tick;<br />

TimeSpan ts = new TimeSpan(tick);


this.Cursor = Cursors.Default;<br />

statusBar1.Text = String.Format(" 任 务 完 成 , 耗 时 {0} 秒 。",<br />

ts.TotalSeconds);<br />

}<br />

private void worker_RateReport(object sender,<br />

Worker.RateReportEventArgs e)<br />

{<br />

this.statusBar1.Text = String.Format(" 已 完 成 {0:P0} ....",<br />

e.Rate);<br />

}<br />

请 注 意 C# 的 挂 接 方 式 (“+=” 运 算 符 )。<br />

到 这 里 为 此 , 你 已 经 看 到 了 事 件 机 制 的 好 处 :Worker 类 的 代 码 和 这 个 Windows Form 没 有 依 赖 关 系 。Worker<br />

类 可 以 单 独 存 在 , 可 以 被 重 复 应 用 到 不 同 的 地 方 。<br />

VB.NET 的 读 者 , 请 查 看 Demo 1F 中 的 VB.NET 标 准 事 件 写 法 , 并 参 考 这 里 的 说 明 , 我 就 不 再 赘 述 了 。<br />

[TOP]<br />

2005 年 1 月 22 日 18:27 - ( 阅 读 :11953; 评 论 :44)<br />

评 论<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

2005-1-22 22:10 | 开 心 就 好<br />

有 没 有 想 法 到 MSDN Webcast 来 讲 一 次 网 络 讲 座 , 把 你 的 这 些 心 得 传 播 给 更 多 的 朋 友 呢 ? 如 果 有 想 法 的<br />

话 , 请 将 你 的 文 章 , 整 理 成 一 个 PPT, 并 且 做 一 个 十 分 钟 左 右 的 录 音 文 件 , 发 送 到<br />

msdnprc^_^microsoft.com(^_^ 变 为 @)。 或 者 发 给 立 楠 也 可 以 。<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

受 益 非 浅 , 我 以 前 从 没 有 这 样 写 过 谢 谢 .<br />

2005-1-24 11:43 | BESTSKY<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

Good<br />

2005-1-25 14:59 | TRULY<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

小 弟 愚 笨 , 折 腾 了 一 个 多 小 时 才 算 是 理 解 了<br />

现 在 用 .net 就 感 觉 越 学 越 是 不 懂 , 今 天 算 是 又 张 了 见 识 了<br />

2005-1-27 17:10 | MOUSEINDARK<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

2005-2-2 17:32 | 生 如 夏 花


前 两 天 面 试 时 碰 到 过 一 道 关 于 事 件 代 理 机 制 的 题 , 今 天 认 识 的 更 清 楚 了 。<br />

# .NET 事 件 模 型 教 程<br />

Ping Back 来 自 :blog.csdn.net<br />

2005-2-4 11:34 | MOREPOWER<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

2005-3-7 15:27 | JUNO MAY<br />

真 的 很 感 谢 你 写 的 这 几 篇 文 章 , 让 我 对 event driven 这 些 很 模 糊 的 概 念 变 得 清 晰 起 来 。<br />

我 是 个 网 站 设 计 师 , 喜 欢 用 php 和 function-oriented 的 方 法 写 应 用 程 序 , 对 M$ 的 东 西 都 很 感 冒 不 太 喜<br />

欢 , 但 是 .NET 的 一 些 概 念 和 方 法 确 实 对 开 发 带 来 便 利 , 尽 管 庞 大 的 framework 和 class 使 得 程 序 变 得<br />

臃 肿 缓 慢 , 但 是 大 大 加 速 开 发 进 程 和 便 于 维 护 。<br />

现 在 我 们 在 用 Prado ( 一 个 借 鉴 了 asp.net 大 部 分 思 想 的 php5 framework) 开 发 应 用 程 序 , 它 山 之 石 可<br />

以 攻 玉 :)<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

文 章 很 不 错 , 有 没 有 想 过 出 书 哦 ....<br />

2005-3-17 16:44 | 冲 浪<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

2005-3-21 1:25 | LIANGYJ<br />

在 《.net 框 架 程 序 设 计 》 中 , 说 到 的 “ 回 调 方 法 的 原 形 应 该 有 一 个 void 返 回 值 , 并 且 接 受 两 个 参 数 , 第 一<br />

个 参 数 为 object 类 型 , 其 指 向 发 送 通 知 的 对 象 , 第 二 个 参 数 为 一 个 继 承 自 EventArgs 的 类 型 , 其 中 包 含<br />

所 有 通 知 接 受 者 需 要 的 附 加 信 息 ”<br />

而 你 在 这 里 定 义 的 event 并 没 有 符 合 这 两 个 规 则 , 那 么 究 竟 是 你 错 ? 还 是 那 本 书 的 错 呢 ? 还 是 有 另 外 的 解<br />

释 呢 ? 请 指 点 一 下 。<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

to Liangyj:<br />

2005-3-21 9:18 | 破 宝<br />

我 相 信 如 果 你 读 完 这 第 一 篇 教 程 全 文 的 话 , 就 不 会 认 为 我 写 的 和 你 那 本 书 有 矛 盾 。 你 再 看 看 最 后 一 个 小 节<br />

“ 向 “.NET Framework 类 库 设 计 指 南 ” 靠 拢 , 标 准 实 现 ” 里 的 内 容 ?<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

xiexie 破 宝 , 收 藏 一 下 不 介 意 吧<br />

2005-4-1 4:01 | JAYE<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

我 是 看 msdn 中 自 定 义 控 件 哪 个 录 像 知 道 事 件 模 型 的 , 你 写 的 不 错<br />

2005-4-9 21:29 | CQHYDZ


# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

2005-4-12 17:55 | JERRY<br />

好 文 啊 !<br />

我 事 件 这 一 张 翻 来 复 去 看 了 几 遍 都 没 看 明 白 。 听 你 这 么 一 讲 解 , 思 路 清 晰 了 很 多 , 多 谢 啊 !<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

写 的 真 好 , 之 前 看 的 msdn, 一 点 也 没 看 懂 , 现 在 总 算 有 点 明 白 了 。<br />

2005-4-21 12:33 | 凉<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

2005-4-28 20:03 | DUR<br />

写 得 很 棒 。:)<br />

偶 有 一 问 。 在 你 的 Worker.DoLongTimeTask 中 写 了 OnStartWork 来 让 Worker 对 象 发 出 一 个 事 件 。<br />

那 么 Form 对 象 是 怎 么 捕 捉 鼠 标 点 了 一 下 的 呢 ?<br />

如 果 想 让 某 接 口 在 收 到 一 脉 冲 时 产 生 一 个 事 件 , 该 怎 么 办 呢 ?<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

看 了 此 文 , 对 事 件 有 了 更 深 的 了 解 !<br />

2005-5-9 3:08 | MYASPX<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

2005-5-12 19:49 | ERICZQWANG<br />

对 那 个 挂 钩 RateReport 事 件 没 有 完 全 理 解 。EndReport 和 StartReport 没 有 挂 钩 , 是 不 是 就 不 执 行 了<br />

呢 ?<br />

// 这 里 我 只 挂 钩 了 RateReport 事 件 做 示 意<br />

worker.RateReport += new Worker.RateReportEventHandler(this.worker_RateReport);<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

2005-5-12 20:02 | 破 宝<br />

to EricZQWang:<br />

不 知 道 你 所 谓 “ 不 执 行 ” 是 指 “ 谁 ” 不 执 行 ?<br />

正 如 文 中 一 开 始 就 说 , 事 件 是 一 个 对 象 发 出 的 消 息 , 到 了 那 个 时 候 它 就 要 发 消 息 , 无 论 是 否 有 人 注 意 这 个<br />

消 息 。<br />

如 果 一 开 始 我 们 对 某 个 事 件 挂 接 了 一 个 handler, 则 在 这 个 事 件 发 生 时 ,handler 被 执 行 。 如 果 不 挂 钩 ,<br />

handler 不 会 执 行 。<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

2005-5-13 8:53 | ERICZQWANG<br />

谢 谢 破 宝 。 我 Share 一 下 自 己 新 的 理 解 :“<br />

// 这 里 挂 钩 了 RateReport 事 件 做 示 意<br />

worker.RateReport += new Worker.RateReportEventHandler(this.worker_RateReport); ”<br />

他 的 作 用 就 是 使 RateReport != null. 在 method:<br />

worker.DoLongTimeTask() 中 ,Worker 总 共 发 出 了 三 个 消 息<br />

OnStartWork,OnRateReport,OnEndReport


在 method: button1_Click() 中 挂 钩 了 OnRateReport 消 息 , 我 觉 得 作 用 就 是 实 例 化 了<br />

Worker.RateReport, 使 RateReport != null,OnStartReport 和 OnEndReport 没 有 挂 钩 ,StartWork<br />

and EndStart ==null.<br />

当 Worker 发 出 OnRateReport 的 消 息 时 , 会 执 行<br />

this.statusBar1.Text = String.Format(" 已 完 成 {0:P0} ....", e.Rate);<br />

但 是 : 当 Worker 发 出 OnStartWork 和 OnEndReport 消 息 时 ,<br />

因 为 StartWork 和 EndReport==null, 所 以 在 Worker 的 Method:OnStartReport 和 OnEndReport<br />

中 什 么 都 没 有 作<br />

满 分 100 的 话 , 这 个 理 解 可 以 打 多 少 分 ?:)<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

2005-5-13 10:55 | 破 宝<br />

应 该 说 从 一 个 面 向 过 程 的 观 点 来 看 , 你 的 理 解 基 本 上 没 什 么 问 题 ( 当 然 指 出 一 点 :“ 消 息 ” 是 指<br />

StartWork,RateRepport,EndWork, 而 不 是 OnXxxx, 后 者 只 是 worker 的 protected 方 法 )。<br />

但 希 望 你 能 够 上 升 到 面 向 对 象 的 角 度 再 来 领 悟 这 个 问 题 。 祝 你 好 运 !<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

代 码 下 不 了 啊 , 能 不 能 给 我 发 一 份<br />

gaofan628@yahoo.com.cn<br />

2005-5-13 22:57 | GAOFAN<br />

谢 谢 先<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

虽 然 我 还 是 个 新 手 .<br />

不 过 还 是 觉 得 应 该 更 正 一 下 sender 和 e 不 是 类 型 而 是 对 象<br />

否 则 你 怎 么 进 行 转 换 呢 ?<br />

类 本 事 是 没 有 转 换 这 个 概 念 的<br />

有 了 继 承 才 有 了 转 换 这 个 概 念<br />

而 转 换 本 身 又 不 是 针 对 类 的<br />

因 为 C# 是 一 门 面 向 对 象 的 设 计 语 言<br />

2005-5-28 20:26 | LHQ<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

2005-5-29 2:13 | 破 宝<br />

to lhq:<br />

不 知 道 是 哪 句 话 中 的 措 辞 不 严 密 , 敬 请 指 出 。<br />

按 照 .net 编 码 指 南 , 事 件 处 理 程 序 的 两 个 参 数 :sender 参 数 的 类 型 应 为 object 类 型 ,e 参 数 的 类 型 应 为<br />

EventArgs 类 型 或 者 EventArgs 类 型 的 子 类 。<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

2005-5-31 11:32 | LHQ<br />

(2) 事 件 委 托 的 “ 规 格 ” 应 该 是 两 个 参 数 : 第 一 个 参 数 是 object 类 型 的 sender, 代 表 发 出 事 件 通 知 的 对 象


( 代 码 中 一 般 是 this 关 键 字 (VB.NET 中 是 Me))。 第 二 个 参 数 e, 应 该 是 EventArgs 类 型 或 者 从<br />

EventArgs 继 承 而 来 的 类 型 ;<br />

在 最 后 几 句 里 , 第 一 句 我 没 仔 细 看 现 在 再 看 这 句 没 有 问 题<br />

不 过 第 二 个 的 e 还 有 点 问 题 我 认 为 e 应 该 是 EventArgs 类 型 或 者 从 EventArgs 继 承 而 来 的 类 型 的 对 象<br />

好 比<br />

class A{}<br />

A a;<br />

那 A 是 类 型 ,a 就 是 对 象<br />

e 就 是 System.EventArgs 以 及 它 所 派 生 的 类 的 对 象<br />

以 上 属 个 人 意 见 , 如 有 不 当 请 指 出<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

to lhq:<br />

多 谢 指 正 , 纯 属 文 法 上 的 考 虑 不 周 。<br />

2005-5-31 17:24 | 破 宝<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

弱 弱 的 问 题 :<br />

2005-6-2 11:52 | JERRY<br />

在 接 收 到 事 件 后 , 我 用 Label 来 显 示 进 度 总 是 没 办 法 显 示 , 只 有 在 事 件 完 成 后 显 示 出 一 个 100%, 而 没 办<br />

法 在 运 行 事 件 过 程 中 显 示 现 在 运 行 到 百 分 之 多 少 了 , 用 了 StatusBar 没 这 个 问 题 。<br />

StatusBar 的 Text 和 Label 的 Text 属 性 不 一 样 吗 ?<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

另 外 , 我 用 progressbar 也 不 能 正 确 显 示 当 前 进 度 。<br />

2005-6-2 13:53 | JERRY<br />

好 象 正 在 运 行 的 任 务 也 会 使 窗 体 陷 入 无 法 响 应 的 状 态 。<br />

有 没 有 办 法 使 进 程 在 运 行 , 而 窗 体 是 有 响 应 的 : 可 以 拖 拽 , 可 以 最 小 话 、 最 大 化 ?<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

2005-7-21 18:32 | LOVALING<br />

看 完 了 , 总 的 来 说 感 觉 C# 的 看 起 来 最 顺 眼 , 个 人 观 点 , 呵 呵 ,<br />

向 “.NET Framework 类 库 设 计 指 南 ” 靠 拢 , 标 准 实 现 这 一 节 我 不 是 十 分 认 同 这 样 的 做 法 ( 虽 然 是 设 计 指<br />

南 ), 本 来 一 个 简 单 的 事 情 被 搞 得 复 杂 了 。 第 一 个 sender 参 数 是 必 要 的 , 使 用 过 委 托 的 都 会 有 这 样 的 需 要 ,<br />

完 全 解 除 了 设 计 上 的 藕 合 , 结 果 丢 失 了 一 些 参 数 , 不 得 不 以 这 样 的 方 式 来 补 偿 。<br />

没 有 必 要 把 一 个 事 件 的 参 数 包 装 到 一 个 类 里 面 , 也 许 这 样 看 起 来 所 有 的 事 件 都 有 一 样 的 外 观 , 但 我 认 为 不<br />

如 直 接 传 递 参 数 来 得 方 便 。 如 下 :<br />

namespace percyboy.EventModelDemo.Demo1E


{<br />

public class Worker<br />

{<br />

private const int MAX = 10000;<br />

public delegate void StartWorkEventHandler(object sender, int totalUnits);<br />

public delegate void RateReportEventHandler(object sender, double rate);<br />

public delegate void EndWorkEventHandler(object sender);<br />

public event StartWorkEventHandler StartWork;<br />

public event EndWorkEventHandler EndWork;<br />

public event RateReportEventHandler RateReport;<br />

public Worker()<br />

{<br />

}<br />

public void DoLongTimeTask()<br />

{<br />

int i;<br />

bool t = false;<br />

double rate;<br />

StartWork(this, MAX);<br />

for (i = 0; i


# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

to lovaling:<br />

2005-7-21 20:58 | 鐮 村 疂<br />

我 的 观 点 是 “ 入 乡 随 俗 ”。<br />

很 多 从 Java 转 C# 的 人 在 书 写 代 码 时 , 把 类 、 方 法 、 属 性 等 等 的 命 名 , 按 照 Java 的 命 名 规 则 去 做 ,<br />

这 样 做 虽 然 没 什 么 坏 处 , 但 看 起 来 总 是 感 觉 不 伦 不 类 的 。 所 以 我 的 观 点 是 “ 入 乡 随 俗 ”, 做 .NET 就 按 微 软<br />

给 大 家 的 约 定 , 没 什 么 不 好 ; 做 Java 就 看 Sun 的 编 码 规 范 。 这 样 做 出 来 的 东 西 , 感 觉 才 是 那 个 “ 味 道 ”。<br />

另 外 , 关 于 事 件 参 数 都 从 EventArgs 继 承 这 一 点 , 我 认 为 是 有 好 处 的 , 不 过 好 处 不 是 定 义 事 件 的 一 方 ,<br />

而 是 调 用 事 件 的 一 方 。<br />

比 如 , 我 们 在 VS.NET 中 写 代 码 时 , 如 果 是 某 事 件 handler 的 代 码 , 你 可 以 直 接 写 e , 然 后 一 “ 点 ” 就 把<br />

所 有 跟 此 事 件 相 关 的 参 数 成 员 点 出 来 了 , 使 用 起 来 还 是 很 方 便 。<br />

我 们 在 使 用 微 软 提 供 的 标 准 类 库 时 , 形 成 了 这 样 的 习 惯 , 那 么 在 定 义 自 己 的 事 件 时 , 没 必 要 一 定 自 创 一 套 。<br />

标 准 类 库 的 事 件 遵 循 一 套 标 准 , 你 自 定 义 的 遵 循 另 一 套 标 准 , 这 样 感 觉 还 是 会 带 来 一 定 程 度 的 混 乱 和 迷 惑 。<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

2005-9-22 15:32 | PUBLIC<br />

在 委 托 (delegate) 简 介 一 节 中 “ 顺 带 地 , 既 然 完 全 面 向 对 象 , 其 实 委 托 本 身 也 是 一 种 对 象 。” 一 句 话<br />

题 出 疑 义 , 我 好 像 记 得 : 委 托 (delegate) 是 一 种 类 (class)。<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

感 谢 楼 上 的 , 终 于 弄 懂 了 C# 里 面 的 事 件 代 理 .<br />

2005-11-5 17:07 | SUNW<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

好 文 章 . 以 前 不 清 楚 的 概 念 现 在 清 楚 了 .<br />

2006-2-13 14:26 | YAO<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

确 实 不 错 , 以 前 比 较 模 糊 的 概 念 现 在 越 来 越 清 楚 了 .<br />

希 望 破 宝 写 出 更 加 好 的 文 章 .<br />

2006-3-7 19:22 | WQXH<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

不 错 , 很 喜 欢 你 的 文 章 , 有 独 道 的 见 解 ...<br />

2006-7-2 12:59 | 野 风<br />

# .NET 事 件 模 型 教 程 ( 一 )<br />

.NET 事 件 模 型 教 程 ( 一 )<br />

2006-8-29 11:52 | AFTER_


目 录<br />

事 件 、 事 件 处 理 程 序 概 念<br />

问 题 描 述 : 一 个 需 要 较 长 时 间 才 能 完 成 的 任 务<br />

高 耦 合 的 实 现<br />

事 件 模 型 的 解 决 方 案 , 简 单 易 懂 的 VB.NET 版 本<br />

委 托 (delegate) 简 介<br />

C# 实 现<br />

向 “.NET Framework 类 库 设 计 指 南 ” 靠 拢 , 标 准 实 现<br />

事 件 、 事 件 处 理 程 序 概 念<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

太 好 了 ! 佩 服<br />

2006-9-8 8:30 | 蛋 蛋<br />

# RE: .NET 事 件 模 型 教 程 ( 一 )<br />

public class Worker<br />

{<br />

private const int MAX = 10000;<br />

2006-11-26 8:45 | ZHANG<br />

public class StartWorkEventArgs : EventArgs<br />

{<br />

private int totalUnits;<br />

public int TotalUnits<br />

{<br />

get { return totalUnits; }<br />

}<br />

public StartWorkEventArgs(int totalUnits)<br />

{<br />

this.totalUnits = totalUnits;<br />

}<br />

}<br />

public class RateReportEventArgs : EventArgs<br />

{<br />

private double rate;<br />

public double Rate<br />

{<br />

get { return rate; }<br />

}


public RateReportEventArgs(double rate)<br />

{<br />

this.rate = rate;<br />

}<br />

}<br />

感 觉 public class StartWorkEventArgs : EventArgs 和 public class RateReportEventArgs :<br />

EventArgs<br />

被 嵌 入 public class Worker 类 内 部 了 , 是 不 是 应 该 移 出 来 , 结 构 更 清 楚 些 。<br />

# 回 复 : .NET 事 件 模 型 教 程 ( 一 )<br />

.NET 事 件 模 型 和 Java 事 件 模 型 的 对 比<br />

2006-12-27 17:51 | 我 考 百 试 通<br />

# 转 载 :.NET 事 件 模 型 教 程 ( 一 )<br />

2007-2-2 11:18 | 狂 风<br />

源 文 来 源 :http://blog.joycode.com/percyboy/archive/2005/01/22/43433.aspx.NET 事 件 模 型<br />

教 程 ( 一 ) 目 录 事 件 、 事 件 ...<br />

# FJTREQJJ<br />

2007-2-9 21:39 | FJTREQJJ<br />

jylxsuia btxcyibl http://alnqrxwg.com/ jsyeamnk<br />

hlqpsxzx [URL=http://tbrbhpom.com/]jewjulvr[/URL]<br />

# 回 复 : .NET 事 件 模 型 教 程 ( 一 )<br />

非 常 佩 服 ! 写 的 太 好 了<br />

2007-2-13 20:39 | LIUYUANBO<br />

# 回 复 : .NET 事 件 模 型 教 程 ( 一 )<br />

DD<br />

2007-4-22 2:42 | 无 情 人<br />

# 回 复 : .NET 事 件 模 型 教 程 ( 一 )<br />

受 益 匪 浅 ! 感 动 ~~<br />

2007-4-25 15:09 | X2<br />

# 回 复 : .NET 事 件 模 型 教 程 ( 一 )<br />

受 益 匪 浅 !<br />

强 烈 的 感 谢 作 者 !<br />

感 动 ~~<br />

2007-4-25 15:10 | X2


.NET 事 件 模 型 教 程 ( 二 )<br />

目 录<br />

• 属 性 样 式 的 事 件 声 明<br />

• 单 播 事 件 和 多 播 事 件<br />

• 支 持 多 播 事 件 的 改 进<br />

属 性 样 式 的 事 件 声 明<br />

在 第 一 节 中 , 我 们 讨 论 了 .NET 事 件 模 型 的 基 本 实 现 方 式 。 这 一 部 分 我 们 将 学 习 C# 语 言 提 供 的 高 级 实 现 方 式 :<br />

使 用 add/remove 访 问 器 声 明 事 件 。( 注 : 本 节 内 容 不 适 用 于 VB.NET。)<br />

我 们 再 来 看 看 上 一 节 中 我 们 声 明 事 件 的 格 式 :<br />

public event [ 委 托 类 型 ] [ 事 件 名 称 ];<br />

这 种 声 明 方 法 , 类 似 于 类 中 的 字 段 (field)。 无 论 是 否 有 事 件 处 理 程 序 挂 接 , 它 都 会 占 用 一 定 的 内 存 空 间 。 一 般<br />

情 况 中 , 这 样 的 内 存 消 耗 或 许 是 微 不 足 道 的 ; 然 而 , 还 是 有 些 时 候 , 内 存 开 销 会 变 得 不 可 接 受 。 比 如 , 类 似<br />

System.Windows.Forms.Control 类 型 具 有 五 六 十 个 事 件 , 这 些 事 件 并 非 每 次 都 会 挂 接 事 件 处 理 程 序 , 如 果<br />

每 次 都 无 端 的 多 处 这 么 多 的 内 存 开 销 , 可 能 就 无 法 容 忍 了 。<br />

好 在 C# 语 言 提 供 了 “ 属 性 ” 样 式 的 事 件 声 明 方 式 :<br />

public event [ 委 托 类 型 ] [ 事 件 名 称 ]<br />

{<br />

add { .... }<br />

remove { .... }<br />

}<br />

如 上 的 格 式 声 明 事 件 , 具 有 add 和 remove 访 问 器 , 看 起 来 就 像 属 性 声 明 中 的 get 和 set 访 问 器 。 使 用 特<br />

定 的 存 储 方 式 ( 比 如 使 用 Hashtable 等 集 合 结 构 ), 通 过 add 和 remove 访 问 器 , 自 定 义 你 自 己 的 事 件 处<br />

理 程 序 添 加 和 移 除 的 实 现 方 法 。<br />

Demo 1G:“ 属 性 ” 样 式 的 事 件 声 明 。 我 首 先 给 出 一 种 实 现 方 案 如 下 ( 此 实 现 参 考 了 .NET Framework SDK 文<br />

档 中 的 一 些 提 示 )( 限 于 篇 幅 , 我 只 将 主 要 的 部 分 贴 在 这 里 ):<br />

public delegate void StartWorkEventHandler(object sender,<br />

StartWorkEventArgs e);<br />

public delegate void RateReportEventHandler(object sender,<br />

RateReportEventArgs e);<br />

// 注 意 : 本 例 中 的 实 现 , 仅 支 持 “ 单 播 事 件 ”。<br />

// 如 需 要 “ 多 播 事 件 ” 支 持 , 请 参 考 Demo 1H 的 实 现 。


为 每 种 事 件 生 成 一 个 唯 一 的 object 作 为 键<br />

static readonly object StartWorkEventKey = new object();<br />

static readonly object EndWorkEventKey = new object();<br />

static readonly object RateReportEventKey = new object();<br />

// 使 用 Hashtable 存 储 事 件 处 理 程 序<br />

private Hashtable handlers = new Hashtable();<br />

// 使 用 protected 方 法 而 没 有 直 接 将 handlers.Add /<br />

handlers.Remove<br />

// 写 入 事 件 add / remove 访 问 器 , 是 因 为 :<br />

// 如 果 Worker 具 有 子 类 的 话 ,<br />

// 我 们 不 希 望 子 类 可 以 直 接 访 问 、 修 改 handlers 这 个 Hashtable。<br />

// 并 且 , 子 类 如 果 有 其 他 的 事 件 定 义 ,<br />

// 也 可 以 使 用 基 类 的 这 几 个 方 法 方 便 的 增 减 事 件 处 理 程 序 。<br />

protected void AddEventHandler(object eventKey, Delegate<br />

handler)<br />

{<br />

lock(this)<br />

{<br />

if (handlers[ eventKey ] == null)<br />

{<br />

handlers.Add( eventKey, handler );<br />

}<br />

else<br />

{<br />

handlers[ eventKey ] = handler;<br />

}<br />

}<br />

}<br />

protected void RemoveEventHandler(object eventKey)<br />

{<br />

lock(this)<br />

{<br />

handlers.Remove( eventKey );<br />

}<br />

}<br />

protected Delegate GetEventHandler(object eventKey)<br />

{<br />

return (Delegate) handlers[ eventKey ];<br />

}


使 用 了 add 和 remove 访 问 器 的 事 件 声 明<br />

public event StartWorkEventHandler StartWork<br />

{<br />

add { AddEventHandler(StartWorkEventKey, value); }<br />

remove { RemoveEventHandler(StartWorkEventKey); }<br />

}<br />

public event EventHandler EndWork<br />

{<br />

add { AddEventHandler(EndWorkEventKey, value); }<br />

remove { RemoveEventHandler(EndWorkEventKey); }<br />

}<br />

public event RateReportEventHandler RateReport<br />

{<br />

add { AddEventHandler(RateReportEventKey, value); }<br />

remove { RemoveEventHandler(RateReportEventKey); }<br />

}<br />

// 此 处 需 要 做 些 相 应 调 整<br />

protected virtual void OnStartWork( StartWorkEventArgs e )<br />

{<br />

StartWorkEventHandler handler =<br />

(StartWorkEventHandler)<br />

GetEventHandler( StartWorkEventKey );<br />

if (handler != null)<br />

{<br />

handler(this, e);<br />

}<br />

}<br />

protected virtual void OnEndWork( EventArgs e )<br />

{<br />

EventHandler handler =<br />

(EventHandler) GetEventHandler( EndWorkEventKey );<br />

}<br />

if (handler != null)<br />

{<br />

handler(this, e);<br />

}<br />

protected virtual void OnRateReport( RateReportEventArgs e )<br />

{


RateReportEventHandler handler =<br />

(RateReportEventHandler)<br />

GetEventHandler( RateReportEventKey );<br />

}<br />

if (handler != null)<br />

{<br />

handler(this, e);<br />

}<br />

public Worker()<br />

{<br />

}<br />

public void DoLongTimeTask()<br />

{<br />

int i;<br />

bool t = false;<br />

double rate;<br />

OnStartWork(new StartWorkEventArgs(MAX) );<br />

for (i = 0; i


在 Demo 1G 给 出 的 解 决 方 案 中 , 你 或 许 已 经 注 意 到 : 如 果 某 一 事 件 被 挂 接 多 次 , 则 后 挂 接 的 事 件 处 理 程 序 ,<br />

将 改 写 先 挂 接 的 事 件 处 理 程 序 。 这 里 就 涉 及 到 一 个 概 念 , 叫 “ 单 播 事 件 ”。<br />

所 谓 单 播 事 件 , 就 是 对 象 ( 类 ) 发 出 的 事 件 通 知 , 只 能 被 外 界 的 某 一 个 事 件 处 理 程 序 处 理 , 而 不 能 被 多 个 事 件 处<br />

理 程 序 处 理 。 也 就 是 说 , 此 事 件 只 能 被 挂 接 一 次 , 它 只 能 “ 传 播 ” 到 一 个 地 方 。 相 对 的 , 就 有 “ 多 播 事 件 ”, 对 象 ( 类 )<br />

发 出 的 事 件 通 知 , 可 以 同 时 被 外 界 不 同 的 事 件 处 理 程 序 处 理 。<br />

打 个 比 方 , 上 一 节 开 头 时 张 三 大 叫 一 声 之 后 , 既 招 来 了 救 护 车 , 也 招 来 了 警 察 叔 叔 ( 问 他 是 不 是 回 不 了 家 了 ),<br />

或 许 还 有 电 视 转 播 车 ( 现 场 直 播 、 采 访 张 三 为 什 么 大 叫 , 呵 呵 )。<br />

多 播 事 件 会 有 很 多 特 殊 的 用 法 。 如 果 以 后 有 机 会 向 大 家 介 绍 Observer 模 式 , 可 以 看 看 Observer 模 式 中 是 怎<br />

么 运 用 多 播 事 件 的 。( 注 : 经 我 初 步 测 试 , 字 段 形 式 的 事 件 声 明 , 默 认 是 支 持 “ 多 播 事 件 ” 的 。 所 以 如 果 在 事 件 种<br />

类 不 多 时 , 我 建 议 你 采 用 上 一 节 中 所 讲 的 字 段 形 式 的 声 明 方 式 。)<br />

[TOP]<br />

支 持 多 播 事 件 的 改 进<br />

Demo1H, 支 持 多 播 事 件 。 为 了 支 持 多 播 事 件 , 我 们 需 要 改 进 存 储 结 构 , 请 参 考 下 面 的 算 法 :<br />

public delegate void StartWorkEventHandler(object sender,<br />

StartWorkEventArgs e);<br />

public delegate void RateReportEventHandler(object sender,<br />

RateReportEventArgs e);<br />

// 为 每 种 事 件 生 成 一 个 唯 一 的 键<br />

static readonly object StartWorkEventKey = new object();<br />

static readonly object EndWorkEventKey = new object();<br />

static readonly object RateReportEventKey = new object();<br />

// 为 外 部 挂 接 的 每 一 个 事 件 处 理 程 序 , 生 成 一 个 唯 一 的 键<br />

private object EventHandlerKey<br />

{<br />

get { return new object(); }<br />

}<br />

// 对 比 Demo 1G,<br />

// 为 了 支 持 “ 多 播 ”,<br />

// 这 里 使 用 两 个 Hashtable: 一 个 记 录 handlers,<br />

// 另 一 个 记 录 这 些 handler 分 别 对 应 的 event 类 型 (event 的 类 型<br />

用 各 自 不 同 的 eventKey 来 表 示 )。<br />

// 两 个 Hashtable 都 使 用 handlerKey 作 为 键 。


使 用 Hashtable 存 储 事 件 处 理 程 序<br />

private Hashtable handlers = new Hashtable();<br />

// 另 一 个 Hashtable 存 储 这 些 handler 对 应 的 事 件 类 型<br />

private Hashtable events = new Hashtable();<br />

protected void AddEventHandler(object eventKey, Delegate<br />

handler)<br />

{<br />

// 注 意 添 加 时 , 首 先 取 了 一 个 object 作 为 handler 的 key,<br />

// 并 分 别 作 为 两 个 Hashtable 的 键 。<br />

}<br />

lock(this)<br />

{<br />

object handlerKey = EventHandlerKey;<br />

handlers.Add( handlerKey, handler );<br />

events.Add( handlerKey, eventKey);<br />

}<br />

protected void RemoveEventHandler(object eventKey, Delegate<br />

handler)<br />

{<br />

// 移 除 时 , 遍 历 events, 对 每 一 个 符 合 eventKey 的 项 ,<br />

// 分 别 检 查 其 在 handlers 中 的 对 应 项 ,<br />

// 如 果 两 者 都 吻 合 , 同 时 移 除 events 和 handlers 中 的 对 应 项 。<br />

//<br />

// 或 许 还 有 更 简 单 的 算 法 , 不 过 我 一 时 想 不 出 来 了 :(<br />

handler )<br />

lock(this)<br />

{<br />

foreach ( object handlerKey in events.Keys)<br />

{<br />

if (events[ handlerKey ] == eventKey)<br />

{<br />

if ( (Delegate)handlers[ handlerKey ] ==<br />

}<br />

}<br />

}<br />

{<br />

}<br />

handlers.Remove( handlers[ handlerKey ] );<br />

events.Remove( events[ handlerKey ] );<br />

break;


}<br />

protected ArrayList GetEventHandlers(object eventKey)<br />

{<br />

ArrayList t = new ArrayList();<br />

lock(this)<br />

{<br />

foreach ( object handlerKey in events.Keys )<br />

{<br />

if ( events[ handlerKey ] == eventKey)<br />

{<br />

t.Add( handlers[ handlerKey ] );<br />

}<br />

}<br />

}<br />

}<br />

return t;<br />

// 使 用 了 add 和 remove 访 问 器 的 事 件 声 明<br />

public event StartWorkEventHandler StartWork<br />

{<br />

add { AddEventHandler(StartWorkEventKey, value); }<br />

remove { RemoveEventHandler(StartWorkEventKey, value); }<br />

}<br />

public event EventHandler EndWork<br />

{<br />

add { AddEventHandler(EndWorkEventKey, value); }<br />

remove { RemoveEventHandler(EndWorkEventKey, value); }<br />

}<br />

public event RateReportEventHandler RateReport<br />

{<br />

add { AddEventHandler(RateReportEventKey, value); }<br />

remove { RemoveEventHandler(RateReportEventKey, value); }<br />

}<br />

// 此 处 需 要 做 些 相 应 调 整<br />

protected virtual void OnStartWork( StartWorkEventArgs e )<br />

{<br />

ArrayList handlers = GetEventHandlers( StartWorkEventKey );


}<br />

foreach(StartWorkEventHandler handler in handlers)<br />

{<br />

handler(this, e);<br />

}<br />

protected virtual void OnEndWork( EventArgs e )<br />

{<br />

ArrayList handlers = GetEventHandlers( EndWorkEventKey );<br />

}<br />

foreach(EventHandler handler in handlers)<br />

{<br />

handler(this, e);<br />

}<br />

protected virtual void OnRateReport( RateReportEventArgs e )<br />

{<br />

ArrayList handlers =<br />

GetEventHandlers( RateReportEventKey );<br />

}<br />

foreach(RateReportEventHandler handler in handlers)<br />

{<br />

handler(this, e);<br />

}<br />

上 面 给 出 的 算 法 , 只 是 给 你 做 参 考 , 应 该 还 有 比 这 个 实 现 更 简 单 、 更 高 效 的 方 式 。<br />

为 了 实 现 “ 多 播 事 件 ”, 这 次 使 用 了 两 个 Hashtable: 一 个 存 储 “handlerKey - handler” 对 , 一 个 存 储<br />

“handlerKey - eventKey” 对 。 相 信 通 过 仔 细 研 读 , 你 可 以 读 懂 这 段 代 码 。 我 就 不 再 赘 述 了 。<br />

[TOP]<br />

2005 年 1 月 22 日 18:35 - ( 阅 读 :6119; 评 论 :13)<br />

评 论<br />

# RE: .NET 事 件 模 型 教 程 ( 二 )<br />

good<br />

2005-1-25 21:51 | HOO<br />

# 对 多 播 事 件 的 一 点 意 见 。<br />

2005-1-31 11:25 | WANG_SOLARIS<br />

看 了 一 下 对 多 播 事 件 的 处 理 方 式 , 总 体 思 路 值 得 肯 定 , 但 在 此 处 对 用 Hashtable 来 存 储 键 值 对 觉 得 有 些 不<br />

妥 。<br />

一 般 按 照 传 统 采 用 非 静 态 成 员 来 标 识 事 件 类 型 的 方 式 , 当 在 客 户 端 为 一 个 事 件 预 定 多 个 事 件 处 理 函 数 的 时


候 , 是 按 照 队 列 的 方 式 来 处 理 的 ( 即 先 进 先 出 原 则 )。<br />

而 在 你 的 代 码 中 采 用 了 Hashtable 就 破 坏 了 这 个 原 则 , 因 为 对 Hashtable 的 遍 历 并 不 是 按 照 插 入 时 的 顺<br />

序 进 行 的 ( 见 上 面 对 events 的 遍 历 )。 所 以 我 建 议 换 成 其 它 支 持 按 插 入 时 顺 序 进 行 遍 历 的 集 合 类 型 , 比 如<br />

ListDictionary 是 个 选 择 , 不 过 当 事 件 很 多 而 对 性 能 要 求 又 很 高 时 , 需 考 虑 其 它 实 现 。( 当 然 上 面 程 序 中 的<br />

handlers 仍 然 可 以 使 用 Hashtable)<br />

# RE: .NET 事 件 模 型 教 程 ( 二 )<br />

谢 谢 wang_solaris 的 建 议 !<br />

2005-1-31 11:51 | 破 宝<br />

我 正 在 准 备 重 写 这 一 部 分 , 因 为 已 经 有 人 给 我 指 出 了 不 确 切 的 地 方 :<br />

其 实 委 托 可 以 是 多 路 的<br />

这 样 的 话 , 就 没 有 必 要 分 别 存 储 多 个 委 托 ,<br />

而 可 以 直 接 挂 接 在 同 一 个 委 托 实 例 上 。<br />

就 是 说 , 委 托 的 一 个 实 例 可 以 同 时 挂 接 多 个 函 数 ,<br />

委 托 是 具 有 +=,-= 运 算 符 的 。<br />

这 一 点 , 我 写 文 章 时 不 了 解 , 给 大 家 介 绍 的 方 法 其 实 走 了 弯 路 。<br />

所 以 我 正 在 准 备 重 写 这 一 部 分 , 暂 时 因 为 年 关 太 忙 无 法 马 上 动 笔 , 请 诸 位 见 谅 !<br />

# .NET 事 件 模 型 教 程<br />

Ping Back 来 自 :blog.csdn.net<br />

2005-2-4 11:34 | MOREPOWER<br />

# RE: .NET 事 件 模 型 教 程 ( 二 )<br />

代 码 下 不 了 啊 , 谁 有 能 否 给 我 一 份 , 感 激 不 尽 。。<br />

gaofan628@yahoo.com.cn<br />

2005-5-11 14:37 | GAOFAN<br />

# RE: .NET 事 件 模 型 教 程 ( 二 )<br />

A 对 象 能 不 能 伪 装 B 对 象 发 出 B 对 象 的 事 件 通 知 。<br />

2005-8-12 15:35 | AYONGWUST<br />

# .NET 事 件 模 型 教 程 ( 二 )<br />

framework<br />

2006-8-30 15:53 | JELINK<br />

# 回 复 : .NET 事 件 模 型 教 程 ( 二 )<br />

.NET 事 件 模 型 和 Java 事 件 模 型 的 对 比<br />

2006-12-27 17:13 | 我 考 百 试 通<br />

# 回 复 : .NET 事 件 模 型 教 程 ( 二 )<br />

2006-12-27 17:50 | 我 考 百 试 通


.NET 事 件 模 型 和 Java 事 件 模 型 的 对 比<br />

# 回 复 : .NET 事 件 模 型 教 程 ( 二 )<br />

我 的 理 解 , 未 经 证 实 :<br />

挂 接 事 件 时 将 运 算 符 由 "+=" 改 为 "=", 事 件 应 该 就 由 多 播 变 成 单 播 了 。<br />

也 就 是 说 .NET 代 理 机 制 本 身 是 以 某 种 方 式 实 现 了 一 个 队 列 。<br />

2007-5-10 10:55 | 座 看 云 起<br />

# 回 复 : .NET 事 件 模 型 教 程 ( 二 )<br />

刚 做 了 实 验 , 挂 接 事 件 时 使 用 "=" 是 不 允 许 的 。 呵 呵 。<br />

2007-5-10 12:11 | 坐 看 云 起<br />

# .NET 技 术 -.NET 理 论 资 料 -.NET 理 论 资 料<br />

综 合 :http://210.27.12.83/jingpin_mms.asp<br />

2007-7-4 14:00 | JASONLI<br />

# 回 复 : .NET 事 件 模 型 教 程 ( 二 )<br />

谢 谢 , 怎 么 一 直 未 见 你 的 改 进 版 呢<br />

2007-7-11 13:33 | ILEX


.NET 事 件 模 型 教 程 ( 三 )<br />

通 过 前 两 节 的 学 习 , 你 已 经 掌 握 了 .NET 事 件 模 型 的 原 理 和 实 现 方 式 。 这 一 节 我 将 介 绍 两 个 替 代 方 案 , 这 些 方<br />

案 并 不 是 推 荐 采 用 的 , 请 尽 量 采 用 事 件 模 型 去 实 现 。 另 外 , 在 本 节 末 尾 , 有 一 段 适 合 熟 悉 Java 语 言 的 读 者 阅 读 ,<br />

讨 论 了 .NET 和 Java 在 “ 事 件 模 型 ” 方 面 的 差 异 。<br />

目 录<br />

• 使 用 接 口 实 现 回 调<br />

• .NET 事 件 模 型 和 Java 事 件 模 型 的 对 比<br />

使 用 接 口 实 现 回 调<br />

事 件 模 型 其 实 是 回 调 函 数 的 一 种 特 例 。 像 前 面 的 例 子 ,Form1 调 用 了 Worker,Worker 反 过 来 ( 通 过 事 件 模<br />

型 ) 让 Form1 改 变 了 状 态 栏 的 信 息 。 这 个 操 作 就 属 于 回 调 的 一 种 。<br />

在 “.NET Framework 类 库 设 计 指 南 ” 中 提 到 了 :“ 委 托 、 接 口 和 事 件 允 许 提 供 回 调 功 能 。 每 个 类 型 都 有 自 己 特 定<br />

的 使 用 特 性 , 使 其 更 适 合 特 定 的 情 况 。”( 参 见 本 地 SDK 版 本 , 在 线 MSDN 版 本 )<br />

事 件 模 型 中 , 事 实 上 也 应 用 了 委 托 来 实 现 回 调 , 可 以 说 , 事 件 模 型 是 委 托 回 调 的 一 个 特 例 。 如 果 有 机 会 , 我 会 在<br />

关 于 多 线 程 的 教 程 中 介 绍 委 托 回 调 在 多 线 程 中 的 应 用 。<br />

这 里 我 先 来 看 看 , 如 何 使 用 接 口 实 现 回 调 功 能 , 以 达 到 前 面 事 件 模 型 实 现 的 效 果 。<br />

Demo 1I: 使 用 接 口 实 现 回 调 。<br />

using System;<br />

using System.Threading;<br />

using System.Collections;<br />

namespace percyboy.EventModelDemo.Demo1I<br />

{<br />

// 注 意 这 个 接 口<br />

public interface IWorkerReport<br />

{<br />

void OnStartWork(int totalUnits);<br />

void OnEndWork();<br />

void OnRateReport(double rate);<br />

}<br />

public class Worker<br />

{<br />

private const int MAX = Consts.MAX;<br />

private IWorkerReport report = null;


public Worker()<br />

{<br />

}<br />

// 初 始 化 时 同 时 指 定 IWorkerReport<br />

public Worker(IWorkerReport report)<br />

{<br />

this.report = report;<br />

}<br />

// 或 者 初 始 化 后 , 通 过 设 置 此 属 性 指 定<br />

public IWorkerReport Report<br />

{<br />

set { report = value; }<br />

}<br />

public void DoLongTimeTask()<br />

{<br />

int i;<br />

bool t = false;<br />

double rate;<br />

if (report != null)<br />

{<br />

report.OnStartWork( MAX );<br />

}<br />

for (i = 0; i


}<br />

}<br />

}<br />

你 可 以 运 行 编 译 好 的 示 例 , 它 可 以 完 成 和 前 面 介 绍 的 事 件 模 型 一 样 的 工 作 , 并 保 证 了 耦 合 度 没 有 增 加 。 调 用<br />

Worker 的 Form1 需 要 做 一 个 IWorkerReport 的 实 现 :<br />

private void button1_Click(object sender, System.EventArgs e)<br />

{<br />

statusBar1.Text = " 开 始 工 作 ....";<br />

this.Cursor = Cursors.WaitCursor;<br />

long tick = DateTime.Now.Ticks;<br />

Worker worker = new Worker();<br />

// 指 定 IWorkerReport<br />

worker.Report = new MyWorkerReport(this);<br />

worker.DoLongTimeTask();<br />

tick = DateTime.Now.Ticks - tick;<br />

TimeSpan ts = new TimeSpan(tick);<br />

this.Cursor = Cursors.Default;<br />

statusBar1.Text = String.Format(" 任 务 完 成 , 耗 时 {0} 秒 。",<br />

ts.TotalSeconds);<br />

}<br />

// 这 里 实 现 IWorkerReport<br />

private class MyWorkerReport : IWorkerReport<br />

{<br />

public void OnStartWork(int totalUnits)<br />

{<br />

}<br />

public void OnEndWork()<br />

{<br />

}<br />

public void OnRateReport(double rate)<br />

{<br />

parent.statusBar1.Text = String.Format(" 已 完 成<br />

{0:P0} ....", rate);


}<br />

private Form1 parent;<br />

}<br />

public MyWorkerReport(Form1 form)<br />

{<br />

this.parent = form;<br />

}<br />

你 或 许 已 经 觉 得 这 种 实 现 方 式 , 虽 然 Worker 类 “ 里 面 ” 可 能 少 了 一 些 代 码 , 却 在 调 用 时 增 加 了 很 多 代 码 量 。 从<br />

重 复 使 用 的 角 度 来 看 , 事 件 模 型 显 然 要 更 方 便 调 用 。 另 外 , 从 面 向 对 象 的 角 度 , 我 觉 得 理 解 了 事 件 模 型 的 原 理 之<br />

后 , 你 会 觉 得 “ 事 件 ” 会 更 亲 切 一 些 。<br />

另 外 ,IWorkerReport 中 包 含 多 个 方 法 , 而 大 多 时 候 我 们 并 不 是 每 个 方 法 都 需 要 , 就 像 上 面 的 例 子 中 那 样 ,<br />

OnStartWork 和 OnEndWork 这 两 个 都 是 空 白 。 如 果 接 口 中 的 方 法 很 多 , 也 会 给 调 用 方 增 加 更 多 的 代 码 量 。<br />

下 载 的 源 代 码 中 还 包 括 一 个 Demo 1J, 它 和 Worker 类 一 起 , 提 供 了 一 个 IWorkerReport 的 默 认 实 现<br />

WorkerReportAdapter( 每 个 方 法 都 是 空 白 )。 这 样 , 调 用 方 只 需 要 从 WorkerReportAdapter 继 承 , 重 写<br />

其 中 需 要 重 写 的 方 法 , 这 样 会 减 少 一 部 分 代 码 量 。 但 我 觉 得 仍 然 是 很 多 。<br />

注 意 , 上 述 的 代 码 , 套 用 ( 仅 仅 是 套 用 , 因 为 它 不 是 事 件 模 型 )“ 单 播 事 件 ” 和 “ 多 播 事 件 ” 的 概 念 来 说 , 它 只 能 支<br />

持 “ 单 播 事 件 ”。 如 果 你 想 支 持 “ 多 播 事 件 ”, 我 想 你 可 以 考 虑 加 入 AddWorkerReport 和<br />

RemoveWorkerReport 方 法 , 并 使 用 Hashtable 等 数 据 结 构 , 存 储 每 一 个 加 入 的 IWorkerReport。<br />

[TOP]<br />

.NET 事 件 模 型 和 Java 事 件 模 型 的 对 比<br />

( 我 对 Java 语 言 的 了 解 不 是 很 多 , 如 果 有 误 , 欢 迎 指 正 !)<br />

.NET 的 事 件 模 型 , 对 于 C#/VB.NET 两 种 主 流 语 言 来 说 , 是 在 语 言 层 次 上 实 现 的 。C# 提 供 了 event 关 键<br />

字 ,VB.NET 提 供 了 Event,RaiseEvent 关 键 字 。 像 前 面 两 节 所 讲 的 那 样 , 它 们 都 有 各 自 的 声 明 事 件 成 员 的<br />

语 法 。 而 Java 语 言 本 身 是 没 有 “ 事 件 ” 这 一 概 念 的 。<br />

从 面 向 对 象 理 论 来 看 ,.NET 的 一 个 类 ( 或 类 的 实 例 : 对 象 ), 可 以 拥 有 : 字 段 、 属 性 、 方 法 、 事 件 、 构 造 函 数 、<br />

析 构 函 数 、 运 算 符 等 成 员 类 型 。 在 Java 中 , 类 只 有 : 字 段 、 方 法 、 构 造 函 数 、 析 构 函 数 、 运 算 符 。Java 的 类<br />

中 没 有 属 性 和 事 件 的 概 念 。( 虽 然 Java Bean 中 将 getWidth、setWidth 的 两 个 方 法 , 间 接 的 转 换 为 一 个<br />

Width 属 性 , 但 Java 依 然 没 有 把 “ 属 性 ” 作 为 一 个 语 言 层 次 的 概 念 提 出 。) 总 之 , 在 语 言 层 次 上 ,Java 不 支 持<br />

事 件 。<br />

Java Swing 是 Java 世 界 中 常 用 的 制 作 Windows 窗 体 程 序 的 一 套 API。 在 Java Swing 中 有 一 套 事 件 模<br />

型 , 来 让 它 的 控 件 ( 比 如 Button 等 ) 拥 有 事 件 机 制 。


Swing 事 件 模 型 , 有 些 类 似 于 本 节 中 介 绍 的 接 口 机 制 。 它 使 用 的 接 口 , 诸 如 ActionListener、KeyListener、<br />

MouseListener( 注 意 : 按 照 Java 的 命 名 习 惯 , 接 口 命 名 不 用 前 缀 I) 等 ; 它 同 时 也 提 供 一 些 接 口 的 默 认 实<br />

现 , 如 KeyAdapter,MouseAdapter 等 , 使 用 方 法 大 概 和 本 节 介 绍 的 类 似 , 它 使 用 的 是<br />

addActionListener/removeActionListener,addKeyListener/removeKeyListener,<br />

addMouseListener/removeMouseListener 等 方 法 , 来 增 减 这 些 接 口 的 。<br />

正 像 本 节 的 例 子 那 样 , 使 用 接 口 机 制 的 Swing 事 件 模 型 , 需 要 书 写 很 多 的 代 码 去 实 现 接 口 或 者 重 写 Adapter。<br />

而 相 比 之 下 ,.NET 事 件 模 型 则 显 得 更 为 轻 量 级 , 所 需 的 挂 接 代 码 仅 一 行 足 矣 。<br />

另 一 方 面 , 我 们 看 到 Swing 的 命 名 方 式 , 将 这 些 接 口 都 命 名 为 Listener, 监 听 器 ; 而 相 比 之 下 ,.NET 事 件<br />

模 型 中 , 对 事 件 的 处 理 被 称 为 handler, 事 件 处 理 程 序 。 一 个 采 用 “ 监 听 ”, 一 个 是 “ 处 理 ”, 我 认 为 这 体 现 了 一 种<br />

思 维 上 的 差 异 。<br />

还 拿 张 三 大 叫 的 例 子 来 讲 ,“ 处 理 ” 模 型 是 说 : 当 张 三 大 叫 事 件 发 生 时 , 外 界 对 它 做 出 处 理 动 作 (handle this<br />

event); 监 听 , 则 是 外 界 一 直 “ 监 听 ” 着 张 三 的 一 举 一 动 (listening), 一 旦 张 三 大 叫 , 监 听 器 就 被 触 发 。 处 理<br />

模 型 是 以 张 三 为 中 心 的 思 维 , 监 听 模 型 则 是 以 外 部 环 境 为 中 心 的 思 维 。<br />

[TOP]<br />

2005 年 1 月 22 日 18:37 - ( 阅 读 :4524; 评 论 :12)<br />

评 论<br />

# RE: .NET 事 件 模 型 教 程 ( 三 )<br />

不 错 , 收 藏 先<br />

2005-1-25 21:52 | HOO<br />

# .NET 事 件 模 型 教 程<br />

Ping Back 来 自 :blog.csdn.net<br />

2005-2-4 11:34 | MOREPOWER<br />

# RE: .NET 事 件 模 型 教 程 ( 三 )<br />

写 得 不 错<br />

2005-4-22 16:15 | AA<br />

# RE: .NET 事 件 模 型 教 程 ( 三 )<br />

2005-7-20 13:26 | COOLLOVE<br />

如 果 我 在 PicBox 上 绘 几 个 矩 形 如 何 用 自 定 义 事 件 ( 例 如 在 mousedown 事 件 中 ) 让 它 们 自 己 区 分 , 并 且<br />

回 应 ??<br />

估 计 没 有 人 试 过 。<br />

# RE: .NET 事 件 模 型 教 程 ( 三 )<br />

very good!thanks a lot!<br />

2006-6-19 10:23 | AA<br />

# RE: .NET 事 件 模 型 教 程 ( 三 )<br />

2006-6-30 10:30 | CREEKSUN


写 的 很 好 阿 , 就 是 看 不 懂 . 我 想 问 您 : 我 想 在 asp.net 网 页 中 调 用 一 个 别 人 编 写 的 成 熟 的 水 质 模 型 , 该 如 何 实<br />

现 ? 求 救 !<br />

# .NET 事 件 模 型 教 程 ( 三 )<br />

framework<br />

2006-8-30 15:54 | JELINK<br />

# 回 复 : .NET 事 件 模 型 教 程 ( 三 )<br />

.NET 事 件 模 型 和 Java 事 件 模 型 的 对 比<br />

2006-12-27 17:13 | 我 考 百 试 通<br />

# 回 复 : .NET 事 件 模 型 教 程 ( 三 )<br />

最 近 正 好 在 做 一 个 项 目<br />

用 C#, 但 是 对 C# 不 是 很 熟 悉 , 尤 其 是 事 件 模 型 , 怎 么 也 不 懂<br />

今 天 看 了 觉 得 相 当 不 错<br />

现 在 就 在 我 的 项 目 中 实 验 呢<br />

2007-1-12 13:33 | MIKE_D<br />

# 回 复 : .NET 事 件 模 型 教 程 ( 三 )<br />

好 文 !!!<br />

2007-3-15 17:27 | 飞 扬 跋 扈<br />

# 回 复 : .NET 事 件 模 型 教 程 ( 三 )<br />

好 文 !!! 好 文 !!! 好 文 !!! 好 文 !!! 好 文 !!!<br />

2007-3-15 17:27 | 飞 扬 跋 扈


.NET 2.0 中 真 正 的 多 线 程 实 例<br />

Real Multi-threading in .NET 2.0<br />

remex1980 翻 译 于 2007-5-16 20:43:21<br />

原 作 者 : Jose Luis Latorre<br />

原 文 地 址 : http://www.codeproject.com/useritems/RealMultiThreading.asp<br />

多 线 程 实 例 源 代 码 ( 保 留 原 文 处 链 接 )<br />

http://www.codeproject.com/useritems/RealMultiThreading/RealMultiThreading_src.zip<br />

简 介<br />

多 线 程 总 是 那 么 让 人 振 奋 。 大 家 都 希 望 能 够 同 时 处 理 很 多 事 情 , 不 过 如 果 我 们 没 有 正 确 的 硬<br />

件 的 话 , 我 们 很 难 达 到 这 点 。 到 目 前 为 止 , 我 们 所 做 的 只 是 分 开 CPU 使 用 较 多 的 工 作 , 使<br />

其 为 后 台 进 程 , 这 样 可 以 使 得 界 面 上 不 被 阻 塞 。<br />

不 过 我 希 望 能 够 得 到 更 好 的 效 果 , 并 充 分 利 用 当 前 最 新 的 多 CPU 效 能 。 因 此 , 我 将 写 一 个<br />

真 正 的 多 线 程 实 例 , 将 会 有 多 个 线 程 作 为 后 台 线 程 在 运 行 。<br />

这 就 是 这 篇 文 章 将 要 写 的 , 不 得 不 说 的 是 , 最 终 的 结 果 实 在 是 让 我 很 激 动 。 希 望 你 也 能 够 发<br />

觉 它 的 用 处 。


在 有 4 个 CPU 的 多 CPU 服 务 器 上 , 我 得 到 了 280% 的 效 果 ( 测 试 的 是 CPU 型 的 任 务 ),<br />

在 一 些 非 CPU 占 用 较 多 的 任 务 中 , 它 可 以 提 高 到 500% 到 1000% 的 性 能 。<br />

背 景<br />

网 上 也 有 不 少 介 绍 .Net 2.0 下 的 多 线 程 的 文 章 , 应 该 说 , 我 从 它 们 中 受 益 颇 多 。 我 正 在 使 用<br />

的 是 BackgroundWorker .Net 2.0 组 件 ( 不 过 也 有 实 现 在 .net 1.1 下 的 代 码 )。<br />

这 里 , 我 列 出 一 些 有 用 的 文 章 链 接 :<br />

来 自 Paul Kimmel 的 很 好 的 介 绍 性 文 章<br />

http://www.informit.com/articles/article.asp?p=459619&seqNum=5&rl=1<br />

来 自 Juval Löwy 的 介 绍 性 文 章 http://www.devx.com/codemag/Article/20639/1954?pf=true<br />

( 必 看 )Joseph Albahari 的 C# 中 使 用 线 程 http://www.albahari.com/threading/part3.html<br />

Michael Weinhardt 写 的 在 Windows Forms 2.0 中 一 个 简 单 安 全 的 多 线 程 , 我 使 用 了 这 个 网<br />

页 中 的 CPU 密 集 型 任 务 , 这 是 他 从 Chris Sell 的 文 章 中 引 用 的 。<br />

http://www.mikedub.net/mikeDubSamples/SafeReallySimpleMultithreadingInWindowsFor<br />

ms20/SafeReallySimpleMultithreadingInWindowsForms20.htm<br />

如 果 你 对 多 线 程 世 界 仍 然 不 是 特 别 熟 悉 或 者 希 望 了 解 最 新 的 .Net 2.0 的<br />

BackgroundWorker 组 件 , 那 么 应 该 好 好 读 读 上 面 的 文 章 。<br />

提 出 的 问 题<br />

任 何 一 个 任 务 …… 无 论 是 CPU 密 集 型 还 是 普 通 的 任 务 :<br />

CPU 密 集 型 : 它 可 以 分 成 一 个 、 两 个 或 多 个 线 程 , 每 个 线 程 会 占 用 一 个 CPU( 这 样 就 使 得<br />

程 序 的 性 能 翻 番 )<br />

普 通 任 务 : 每 一 个 顺 序 执 行 的 普 通 任 务 , 在 进 行 数 据 存 储 或 使 用 一 个 web service 的 时 候 都<br />

会 有 一 些 延 迟 。 所 有 的 这 些 , 都 意 味 着 这 些 没 有 使 用 的 时 间 对 于 用 户 或 任 务 本 身 来 说 有 了 浪<br />

费 。 这 些 时 间 将 被 重 新 安 排 , 并 将 被 并 行 的 任 务 使 用 , 不 会 再 丢 失 。 也 就 是 说 , 如 果 有 100<br />

个 100ms 延 迟 的 任 务 , 它 们 在 单 线 程 模 型 和 20 个 线 程 模 型 的 性 能 差 距 会 达 到 1000%。<br />

我 们 说 , 如 果 要 处 理 一 个 创 建 一 个 网 站 多 个 块 的 任 务 , 不 是 顺 序 的 执 行 , 而 是 花 1-4 秒 钟<br />

把 所 有 的 section 创 建 好 ; 商 标 , 在 线 用 户 , 最 新 文 章 , 投 票 工 具 等 等 …… 如 果 我 们 能 够<br />

异 步 地 创 建 它 们 , 然 后 在 发 送 给 用 户 , 会 怎 么 样 ? 我 们 就 会 节 省 很 多 webservice 的 调 用 ,<br />

数 据 库 的 调 用 , 许 多 宝 贵 的 时 间 …… 这 些 调 用 会 更 快 地 执 行 。 看 上 去 是 不 是 很 诱 人 ?<br />

解 决 方 案 如 下 :<br />

调 用 BackgroundWorker, 正 如 我 们 想 要 的 那 样 , 我 们 会 继 承 它 。 后 台 worker 会 帮 助 我 们<br />

建 立 一 个 “Worker”, 用 于 异 步 地 做 一 个 工 作 。


我 们 想 做 的 是 建 立 一 个 工 厂 Factory( 只 是 为 了 面 向 对 象 的 设 计 , 于 设 计 模 式 无 关 ), 任 务<br />

会 放 在 在 这 个 Factory 中 执 行 。 这 意 味 着 , 我 们 将 有 一 类 的 任 务 , 一 些 进 程 , 一 些 知 道 如 何<br />

执 行 任 务 的 worker。<br />

当 然 我 们 需 要 一 个 负 责 分 配 任 务 给 这 些 worker 的 manager, 告 诉 这 些 worker 当 它 们 做 完<br />

一 步 或 全 部 时 , 做 什 么 事 情 。 当 然 , 我 们 也 需 要 manager 能 够 告 诉 worker 停 止 当 前 的 任 务 。<br />

它 们 也 需 要 休 息 啊 :) 当 manager 说 停 止 的 时 候 , 它 们 就 应 该 停 止 。<br />

我 们 将 会 从 底 至 上 地 解 释 这 些 , 首 先 从 Worker 说 起 , 然 后 再 继 续 Manager。<br />

Worker<br />

它 是 Background worker 的 继 承 类 , 我 们 构 建 一 个 构 造 函 数 , 并 分 配 两 个 BackgroundWorker<br />

的 属 性 , 分 别 是 WorkerReportsProgress 和 WorkerSupportsCancellation, 它 们 的 功 能 就<br />

向 其 名 字 的 意 义 一 样 : 报 告 进 度 , 停 止 任 务 。 每 个 Worker 还 有 一 个 id,Manager 将 会 通 过<br />

这 个 id 控 制 它 们 。<br />

public class MTWorker : BackgroundWorker<br />

{<br />

#region Private members<br />

private int _idxLWorker = 0;<br />

#endregion<br />

#region Properties<br />

public int IdxLWorker<br />

{<br />

get { return _idxLWorker; }<br />

set { _idxLWorker = value; }<br />

}<br />

#endregion<br />

#region Constructor<br />

public MTWorker()<br />

{<br />

WorkerReportsProgress = true;<br />

WorkerSupportsCancellation = true;<br />

}<br />

public MTWorker(int idxWorker)<br />

: this()<br />

{<br />

_idxLWorker = idxWorker;<br />

}<br />

#endregion


另 外 , 我 们 将 重 载 BackgroundWorker 的 一 些 函 数 。 事 实 上 , 最 有 意 思 的 是 , 究 竟 谁 在 做<br />

真 正 的 工 作 ? 它 就 是 OnDoWork, 当 我 们 invoke 或 者 启 动 多 线 程 的 时 候 , 它 就 会 被 调 用 。<br />

在 这 里 , 我 们 启 动 任 务 、 执 行 任 务 、 取 消 和 完 成 这 个 任 务 。<br />

我 加 了 两 个 可 能 的 任 务 , 一 个 是 普 通 型 的 , 它 会 申 请 并 等 待 文 件 系 统 、 网 络 、 数 据 库 或<br />

Webservices 的 调 用 。 另 一 个 是 CPU 密 集 型 的 任 务 : 计 算 PI 值 。 你 可 以 试 试 增 加 或 减 少 线<br />

程 数 量 后 , 增 加 或 是 减 少 的 延 迟 ( 我 的 意 思 是 增 减 Worker 的 数 量 )<br />

OnDoWork 方 法 的 代 码<br />

protected override void OnDoWork(DoWorkEventArgs e)<br />

{<br />

//Here we receive the necessary data for doing the work...<br />

//we get an int but it could be a struct, class, whatever..<br />

int digits = (int)e.Argument;<br />

double tmpProgress = 0;<br />

int Progress = 0;<br />

String pi = "3";<br />

// This method will run on a thread other than the UI thread.<br />

// Be sure not to manipulate any Windows Forms controls created<br />

// on the UI thread from this method.<br />

this.ReportProgress(0, pi);<br />

//Here we tell the manager that we start the job..<br />

Boolean bJobFinished = false;<br />

int percentCompleteCalc = 0;<br />

String TypeOfProcess = "NORMAL"; //Change to "PI" for a cpu intensive task<br />

//Initialize calculations<br />

while (!bJobFinished)<br />

{<br />

if (TypeOfProcess == "NORMAL")<br />

{<br />

#region Normal Process simulation, putting a time<br />

delay to emulate a wait-for-something<br />

while (!bJobFinished)<br />

{<br />

if (CancellationPending)<br />

{<br />

e.Cancel = true;<br />

return; //break<br />

}<br />

//Perform another calculation step<br />

Thread.Sleep(250);


percentCompleteCalc = percentCompleteCalc + 10;<br />

if (percentCompleteCalc >= 100)<br />

bJobFinished = true;<br />

else<br />

ReportProgress(percentCompleteCalc, pi);<br />

}<br />

#endregion<br />

}<br />

else<br />

{<br />

#region Pi Calculation - CPU intensive job,<br />

beware of it if not using threading ;) !!<br />

//PI Calculation<br />

if (digits > 0)<br />

{<br />

pi += ".";<br />

for (int i = 0; i < digits; i += 9)<br />

{<br />

// Work out pi. Scientific bit :-)<br />

int nineDigits = NineDigitsOfPi.StartingAt(i + 1);<br />

int digitCount = System.Math.Min(digits - i, 9);<br />

string ds = System.String.Format("{0:D9}", nineDigits);<br />

pi += ds.Substring(0, digitCount);<br />

}<br />

// Show progress<br />

tmpProgress = (i + digitCount);<br />

tmpProgress = (tmpProgress / digits);<br />

tmpProgress = tmpProgress * 100;<br />

Progress = Convert.ToInt32(tmpProgress);<br />

ReportProgress(Progress, pi);<br />

// Deal with possible cancellation<br />

if (CancellationPending) //If the manager says to stop, do so..<br />

{<br />

bJobFinished = true;<br />

e.Cancel = true;<br />

return;<br />

}<br />

}<br />

}<br />

bJobFinished = true;<br />

#endregion<br />

}


ReportProgress(100, pi); //Last job report to the manager ;)<br />

e.Result = pi; //Here we pass the final result of the Job<br />

}<br />

Manager<br />

这 是 一 个 很 有 趣 的 地 方 , 我 确 信 它 有 很 大 的 改 进 空 间 - 欢 迎 任 何 的 评 论 和 改 进 ! 它 所 做 的 是<br />

给 每 个 线 程 生 成 和 配 置 一 个 Worker, 然 后 给 这 些 Worker 安 排 任 务 。 目 前 , 传 给 Worker<br />

的 参 数 是 数 字 , 但 是 它 能 够 传 送 一 个 包 含 任 务 定 义 的 类 或 结 构 。 一 个 可 能 的 改 进 是 选 择 如 何<br />

做 这 些 内 部 工 作 的 策 略 模 式 。<br />

调 用 InitManager 方 法 配 置 任 务 , 和 它 的 数 量 等 属 性 。 然 后 , 创 建 一 个 多 线 程 Worker 的 数<br />

组 , 配 置 它 们 。<br />

配 置 的 代 码 如 下 :<br />

private void ConfigureWorker(MTWorker MTW)<br />

{<br />

//We associate the events of the worker<br />

MTW.ProgressChanged += MTWorker_ProgressChanged;<br />

MTW.RunWorkerCompleted += MTWorker_RunWorkerCompleted;<br />

}<br />

Like this, the Worker’s subclassed thread management Methods are linked to the<br />

Methods held by the Manager. Note that with a Strategy pattern implemented we could<br />

assign these to the proper manager for these methods.<br />

最 主 要 的 方 法 是 AssignWorkers, 它 会 检 查 所 有 的 Worker, 如 果 发 现 没 有 任 务 的 Worker,<br />

就 分 配 一 个 任 务 给 它 。 直 到 扫 描 一 遍 之 后 , 没 有 发 现 任 何 有 任 务 的 Worker, 这 样 就 意 味 这<br />

任 务 结 束 了 。 不 需 要 再 做 别 的 了 !<br />

代 码 如 下 :<br />

public void AssignWorkers()<br />

{<br />

Boolean ThereAreWorkersWorking = false;<br />

//We check all workers that are not doing a job... and assign a new one<br />

foreach (MTWorker W in _arrLWorker)<br />

{<br />

if (W.IsBusy == false)<br />

{<br />

//If there are still jobs to be done...<br />

//we assign the job to the free worker<br />

if (_iNumJobs > _LastSentThread)<br />

{


We control the threads associated to a worker<br />

//(not meaning the jobs done) just 4 control.<br />

_LastSentThread = _LastSentThread + 1;<br />

W.JobId = _LastSentThread; //We assign the job number..<br />

W.RunWorkerAsync(_iPiNumbers); //We pass the parameters for the job.<br />

ThereAreWorkersWorking = true;<br />

//We have at least this worker we just assigned the job working..<br />

}<br />

}<br />

else<br />

{<br />

ThereAreWorkersWorking = true;<br />

}<br />

}<br />

if (ThereAreWorkersWorking == false)<br />

{<br />

//This means that no worker is working and no job has been assigned.<br />

//this means that the full package of jobs has finished<br />

//We could do something here...<br />

Button BtnStart = (Button)FormManager.Controls["btnStart"];<br />

Button BtnCancel = (Button)FormManager.Controls["btnCancel"];<br />

BtnStart.Enabled = true;<br />

BtnCancel.Enabled = false;<br />

MessageBox.Show("Hi, I'm the manager to the boss (user): " +<br />

"All Jobs have finished, boss!!");<br />

}<br />

}<br />

只 要 有 任 务 完 成 , 这 个 方 法 就 会 被 调 用 。 从 而 , 保 证 所 有 的 任 务 能 够 完 成 。<br />

我 们 还 通 过 一 个 属 性 链 接 到 Form 上 , 这 样 我 们 就 能 向 UI 上 输 出 我 们 想 要 的 任 何 消 息 了 。<br />

当 然 , 你 可 能 想 链 接 到 其 它 的 一 些 类 , 不 过 这 是 最 基 本 最 通 用 的 。<br />

Well… improving it we could get a BackgroundManager for all our application needs..<br />

界 面<br />

连 接 到 界 面 上 , 并 不 是 最 主 要 的 功 能 。 这 一 部 分 的 代 码 量 非 常 少 , 也 很 简 单 : 在 Manager<br />

中 添 加 一 个 引 用 , 并 在 form 的 构 造 函 数 中 配 置 它 。<br />

在 一 个 按 钮 中 , 执 行 Manager 类 的 LaunchManagedProcess 方 法 。<br />

private MTManager LM;


public Form1()<br />

{<br />

InitializeComponent();<br />

LM = new MTManager(this, 25);<br />

LM.InitManager();<br />

}<br />

private void btnStart_Click(object sender, EventArgs e)<br />

{<br />

btnStart.Enabled = false;<br />

btnCancel.Enabled = true;<br />

LM.LaunchManagedProcess();<br />

}<br />

private void btnCancel_Click(object sender, EventArgs e)<br />

{<br />

LM.StopManagedProcess();<br />

btnCancel.Enabled = false;<br />

btnStart.Enabled = true;<br />

}<br />

( 下 面 的 自 己 看 喽 :)<br />

Trying it!<br />

This is the funniest part, changing the properties of how many threads to run<br />

simultaneously and how many Jobs to be processed and then try it on different CPU’s…<br />

ah, and of course, change the calculation method from a CPU-intensive task to a normal<br />

task with a operation delay...<br />

I would love to know your results and what have you done with this, any feedback would<br />

be great!!<br />

Exercises For You…<br />

This is not done! It could be a MultiThreadJob Framework if there is being done the<br />

following:<br />

Implement a Strategy pattern that determines the kind of Worker to produce (with a<br />

factory pattern) so we will be able to do different kind of jobs inside the same factory..<br />

what about migrating a database and processing each table in a different way… or<br />

integrating systems with this engine…<br />

Implement -or extend- the strategy pattern for determining the treatment for the Input data<br />

and the result data of the jobs. We could too set-up a factory for getting the classes into a<br />

operating environment.<br />

Optimize the AssignWorkers engine – I am pretty sure it can be improved.<br />

Improve the WorkerManager class in order to be able to attach it to another class instead


to only a form.<br />

Send me the code! I Would love to hear from you and what have you done.<br />

About Jose Luis Latorre<br />

Professional developer since 1991, having developed on multiple systems and<br />

languages since then, from Unix, as400, lotus notes, flash, javascript, asp, prolog, vb, c++,<br />

vb.Net, C#...<br />

Now I'm focused on .Net development, both windows and web with two-three year<br />

experience on both and fully up-to date with 2.0 .Net in both Vb and C#<br />

Also have experience with SQL server 2005 and Business Intelligence.<br />

Jose Luis Lives in Barcelona, Spain, with his cat Pancho. To contact Jose Luis, email him<br />

at<br />

joslat@gmail.com.<br />

Click here to view Jose Luis Latorre's online profile.<br />

Copyright MSProject 2006-2007. 转 载 本 站 文 章 必 须 经 过 作 者 本 人 或 管 理 员 的 同 意


C# 中 在 线 程 中 访 问 主 Form 控 件 的 问 题<br />

C# 不 允 许 直 接 从 线 程 中 访 问 Form 里 的 控 件 , 比 如 希 望 在 线 程 里 修 改 Form 里 的<br />

一 个 TextBox 的 内 容 等 等 , 唯 一 的 做 法 是 使 用 Invoke 方 法 , 下 面 是 一 个 MSDN<br />

里 的 Example, 很 说 明 问 题 :<br />

using System;<br />

using System.Drawing;<br />

using System.Windows.Forms;<br />

using System.Threading;<br />

public class MyFormControl : Form<br />

{<br />

public delegate void AddListItem(String myString);<br />

public AddListItem myDelegate;<br />

private Button myButton;<br />

private Thread myThread;<br />

private ListBox myListBox;<br />

public MyFormControl()<br />

{<br />

myButton = new Button();<br />

myListBox = new ListBox();<br />

myButton.Location = new Point(72, 160);<br />

myButton.Size = new Size(152, 32);<br />

myButton.TabIndex = 1;


myButton.Text = "Add items in list box";<br />

myButton.Click += new EventHandler(Button_Click);<br />

myListBox.Location = new Point(48, 32);<br />

myListBox.Name = "myListBox";<br />

myListBox.Size = new Size(200, 95);<br />

myListBox.TabIndex = 2;<br />

ClientSize = new Size(292, 273);<br />

Controls.AddRange(new Control[] {myListBox,myButton});<br />

Text = " 'Control_Invoke' example ";<br />

myDelegate = new AddListItem(AddListItemMethod);<br />

}<br />

static void Main()<br />

{<br />

MyFormControl myForm = new MyFormControl();<br />

myForm.ShowDialog();<br />

}<br />

public void AddListItemMethod(String myString)<br />

{<br />

myListBox.Items.Add(myString);<br />

}<br />

private void Button_Click(object sender, EventArgs e)<br />

{


myThread = new Thread(new ThreadStart(ThreadFunction));<br />

myThread.Start();<br />

}<br />

}<br />

private void ThreadFunction()<br />

{<br />

MyThreadClass myThreadClassObject = new MyThreadClass(this);<br />

myThreadClassObject.Run();<br />

}<br />

public class MyThreadClass<br />

{<br />

MyFormControl myFormControl1;<br />

public MyThreadClass(MyFormControl myForm)<br />

{<br />

myFormControl1 = myForm;<br />

}<br />

String myString;<br />

public void Run()<br />

{<br />

for (int i = 1; i


BackgroudWorker 范 例<br />

在 很 多 场 合 下 , 你 需 要 在 主 (UI) 线 程 中 运 行 一 些 比 较 耗 时 间 的 任 务 , 比 如 以 下 的 任 务<br />

1 Image downloads<br />

2 Web service invocations<br />

3 File downloads and uploads (including for peer-to-peer applications)<br />

4 Complex local computations<br />

5 Database transactions<br />

6 Local disk access, given its slow speed relative to memory access<br />

这 个 时 候 UI 就 会 陷 入 一 种 假 死 的 状 态 , 会 给 用 户 带 来 一 种 很 不 好 的 体 验 . 如 何 在 这 里 发 挥 多 线 程 的 优 势 以 改 善 用<br />

户 体 验 ? .Net2.0 的 System.ComponentModel.BackgroundWorker 为 我 们 提 供 了 一 个 很 方 便 的 解 决 方 法 .<br />

在 vs.net2005 101 sample 中 提 供 了 一 个 计 算 素 数 的 例 子 , 不 过 那 个 例 子 并 没 有 全 面 演 示<br />

BackgroundWorker 的 能 力 , 尤 其 是 没 有 对 线 程 工 作 过 程 (ReportProgress) 中 的 能 力 做 比 较 好 的 演 示 . 因 此 我<br />

重 新 做 了 一 个 Demo.<br />

这 个 例 子 很 简 单 , 就 是 将 左 边 列 表 中 的 内 容 移 至 的 右 边 , 用 一 个 进 度 条 来 显 示 移 动 的 进 度 , 当 然 既 然 是<br />

BackgroundWorker 这 个 时 候 主 界 面 可 以 进 行 其 他 操 作 .<br />

本 文 的 源 代 码 提 供 下 载 , 其 中 有 详 细 注 释 , 所 以 我 在 此 简 要 介 绍 一 下 需 要 注 意 的 地 方 .<br />

BackgroundWorker 主 要 通 过 对 DoWork ProgressChanged RunWorkerCompleted 三 个 事 件<br />

的 处 理 来 完 成 任 务 . 需 要 注 意 在 DoWork 中 不 能 直 接 操 作 主 界 面 的 元 素 . 比 如 你 在 MainForm 类 中 启 动 了 一 个<br />

BackgroundWorker, 在 DoWork 的 处 理 方 法 中 不 能 直 接 调 用 任 何 MainForm 中 的 成 员 变 量 . 但 是 在<br />

ProgressChanged 和 RunWorkerCompleted 的 事 件 处 理 中 则 无 此 限 制 , 可 以 在 后 台 线 程 中 直 接 调 用 主 线 程<br />

中 的 元 素 , 这 是 BackgroundWorker 中 最 有 亮 点 的 地 方 . 虽 然 在 DoWork 的 处 理 方 法 中 不 能 调 用 但 是 它 也 提 供<br />

了 参 数 传 递 的 方 法 , 可 以 间 接 调 用 . 示 例 如 下 :<br />

27 //If your background operation requires a parameter,<br />

28 //call System.ComponentModel.BackgroundWorker.RunWorkerAsync<br />

29 //with your parameter. Inside the System.ComponentModel.BackgroundWorker.DoWork<br />

30 //event handler, you can extract the parameter from the


31 //System.ComponentModel.DoWorkEventArgs.Argument property.<br />

32 worker.RunWorkerAsync(leftList);<br />

27 private void worker_DoWork(object sender, DoWorkEventArgs e)<br />

28 {<br />

29 MoveList((BackgroundWorker)sender,e);<br />

30 }<br />

31<br />

32 private void MoveList(BackgroundWorker worker,DoWorkEventArgs e)<br />

33 { //get leftList in Main UI Thread from arguments<br />

34 IList list = e.Argument as IList;<br />

35 //...<br />

36 }<br />

而 在 ProgressChanged 和 RunWorkerCompleted 事 件 的 处 理 方 法 中 则 更 加 简 单 .<br />

e)<br />

27 private void worker_ProgressChanged(object sender, ProgressChangedEventArgs<br />

28 {<br />

29 //Add string to the right listBox, we use rightList in Main UI Thread directly<br />

30 rightList.Add(e.UserState as string);<br />

31 }<br />

上 述 原 则 可 以 说 是 BackgroundWorker 最 需 要 注 意 的 地 方 .<br />

另 外 一 个 容 易 被 人 粗 心 漏 过 的 地 方 是 有 关 属 性 的 设 置 .


如 果 你 要 使 BackgroundWorker 支 持 进 度 汇 报 和 取 消 功 能 别 忘 了 在 初 始 化 的 时 候 为 下 面 两 个 属 性 赋 值 .<br />

// Specify that the background worker provides progress notifications<br />

worker.WorkerReportsProgress = true;<br />

// Specify that the background worker supports cancellation<br />

worker.WorkerSupportsCancellation = true;<br />

其 它 部 分 就 让 大 家 自 己 看 代 码 吧 .<br />

BackgroundWorker 内 部 实 现 是 基 于 delegate 的 异 步 调 用 .<br />

dotnet 中 一 个 重 要 组 件 -BackgroundWorker - Strive for perfection,Settle for excellence! - 博 客 园<br />

dotnet 中 一 个 重 要 组 件 -BackgroundWorker<br />

最 近 一 直 在 看 wse3.0, 从 一 个 例 子 中 偶 然 的 收 获 。 虽 然 通 过 后 台 操 作 , 从 而 减 少 用 户 交 互 时 的 “ 僵 硬 ” 体 验 一<br />

直 是 每 个 程 序 员 的 追 求 , 在 今 天 这 样 ajax 的 时 代 里 面 更 加 显 的 重 要 。 一 切 为 了 用 户 , 一 切 为 了 更 丰 富 愉 快 的 体<br />

验 。 本 文 并 不 是 ajax 相 关 的 东 东 。 伟 大 的 BackgroundWorker!<br />

BackgroundWorker 类 允 许 您 在 单 独 的 专 用 线 程 上 运 行 操 作 。 耗 时 的 操 作 ( 如 下 载 和 数 据 库 事 务 ) 在 长 时 间 运<br />

行 时 可 能 会 导 致 用 户 界 面 (UI)<br />

似 乎 处 于 停 止 响 应 状 态 。 如 果 您 需 要 能 进 行 响 应 的 用 户 界 面 , 而 且 面 临 与 这 类 操 作 相 关 的 长 时 间 延 迟 , 则 可 以 使<br />

用 BackgroundWorker 类 方 便 地 解 决 问 题 。<br />

您 必 须 非 常 小 心 , 确 保 在 DoWork 事 件 处 理 程 序 中 不 操 作 任 何 用 户 界 面 对 象 。 而 应 该 通 过<br />

ProgressChanged 和<br />

RunWorkerCompleted 事 件 与 用 户 界 面 进 行 通 信<br />

使 用 方 式 :<br />

1。 给 组 件 注 册 事 件 处 理 方 法 :<br />

// 正 式 做 事 情 的 地 方<br />

backgroundWorker1.DoWork +=<br />

new DoWorkEventHandler(backgroundWorker1_DoWork);<br />

// 任 务 完 称 时 要 做 的 , 比 如 提 示 等 等<br />

backgroundWorker1.RunWorkerCompleted +=<br />

new<br />

RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);


任 务 进 行 时 , 报 告 进 度<br />

backgroundWorker1.ProgressChanged +=<br />

new<br />

ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);<br />

2。 添 加 具 体 事 件 处 理 方 法<br />

DoWork 调 用 RunWorkerAsync 时 发 生 。<br />

ProgressChanged 调 用 ReportProgress 时 发 生 。<br />

RunWorkerCompleted 当 后 台 操 作 已 完 成 、 被 取 消 或 引 发 异 常 时 发 生 。<br />

1// 这 个 例 子 没 有 做 什 么 事 情 , 完 全 是 看 看 效 果 而 已 , 但 同 时 有 个 大 问 题 , 我 也 不 知 道 为 什 么 , 没 有 去 除 僵 硬 情<br />

况 。<br />

2namespace BackgroundWorkerTest<br />

3{<br />

4 public partial class Form1 : Form<br />

5 {<br />

6 public Form1()<br />

7 {<br />

8 InitializeComponent();<br />

9 InitialzeBackgroundWorker();<br />

10 }<br />

11<br />

12 private void InitialzeBackgroundWorker()<br />

13 {<br />

14 this.backgroundWorker1.DoWork+=new<br />

DoWorkEventHandler(backgroundWorker1_DoWork);<br />

15 this.backgroundWorker1.ProgressChanged+=new<br />

ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);<br />

16 this.backgroundWorker1.RunWorkerCompleted+=new<br />

RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);<br />

17 }<br />

18<br />

19<br />

20 private void backgroundWorker1_RunWorkerCompleted(object sender,<br />

RunWorkerCompletedEventArgs e)<br />

21 {<br />

22 MessageBox.Show("Completly");<br />

23 }<br />

24<br />

25 private void backgroundWorker1_ProgressChanged(object sender,<br />

ProgressChangedEventArgs e)<br />

26 {<br />

27 this.progressBar1.Value = e.ProgressPercentage;


28 }<br />

29 private void backgroundWorker1_DoWork(object sender,DoWorkEventArgs e)<br />

30 {<br />

31 e.Result = ComputeFibonacci(this.backgroundWorker1, e);<br />

32 }<br />

33<br />

34 private int ComputeFibonacci(object sender, DoWorkEventArgs e)<br />

35 {<br />

36<br />

37 for (int i = 0; i < 100000; i++)<br />

38 {<br />

39 if (this.backgroundWorker1.CancellationPending)<br />

40 {<br />

41 e.Cancel = true;<br />

42<br />

43 }<br />

44 else<br />

45 {<br />

46 this.backgroundWorker1.ReportProgress(i);<br />

47 }<br />

48<br />

49 }<br />

50 return 0;<br />

51<br />

52 }<br />

53<br />

54<br />

55 private void button1_Click(object sender, EventArgs e)<br />

56 {<br />

57 this.backgroundWorker1.RunWorkerAsync();<br />

58 }<br />

59<br />

60 private void button2_Click(object sender, EventArgs e)<br />

61 {<br />

62 this.backgroundWorker1.CancelAsync();<br />

63 }<br />

64 }<br />

65}<br />

给 出 另 一 种 使 用 : 继 承 BackgroundWorker:<br />

namespace UploadWinClient<br />

{<br />

/**//// <br />

/// Contains common functionality for the upload and download classes


This class should really be marked abstract but VS doesn't like that<br />

because it can't draw it as a component then :(<br />

/// <br />

public class FileTransferBase : BackgroundWorker<br />

{<br />

public FileTransferBase()<br />

{<br />

base.WorkerReportsProgress = true;<br />

base.WorkerSupportsCancellation = true;<br />

}<br />

protected override void Dispose(bool disposing)<br />

{<br />

if(this.HashThread != null && this.HashThread.IsAlive)<br />

this.HashThread.Abort();<br />

base.Dispose(disposing);<br />

}<br />

e)<br />

protected override void OnRunWorkerCompleted(RunWorkerCompletedEventArgs<br />

{<br />

if(this.HashThread != null && this.HashThread.IsAlive)<br />

this.HashThread.Abort();<br />

base.OnRunWorkerCompleted(e);<br />

}<br />

protected override void OnDoWork(DoWorkEventArgs e)<br />

{<br />

// make sure we can connect to the web service. if this step is not<br />

done here, it will retry 50 times because of the retry code<br />

this.WebService.Ping();<br />

base.OnDoWork(e);<br />

}<br />

}// 截 取 的 部 分 代 码 。<br />

// 从 中 给 出 我 们 要 override 的 一 些 方 法


BackgroundWorker 在 长 时 间 的 webservices 中 特 别 有 用 。<br />

posted on 2006-07-05 10:25 flyingchen 阅 读 (708) 评 论 (1) 编 辑 收 藏 引 用 网 摘 所 属 分 类 : DotNet<br />

技 术<br />

评 论<br />

# 关 于 net2.0 里 面 新 出 现 的 类 backgroundworker 的 应 用 [TrackBack] 2007-01-11 16:16 天 龙<br />

这 是 一 个 在 .net2.0 里 面 新 出 现 的 类 , 用 于 执 行 后 台 比 较 长 的 任 务 而 又 想 能 和 UI 有 点 操 作 的 应 用 里 面 。 普 通 情<br />

况 下 , 你 点 击 一 个 按 钮 , 去 后 台 执 行 一 个 process, 如 果 你 想 得 到 结 果 , 就 得 等 这 个 proces...<br />

这 是 一 个 在 .net2.0 里 面 新 出 现 的 类 , 用 于 执 行 后 台 比 较 长 的 任 务 而 又 想 能 和 UI 有 点 操 作 的 应 用 里 面 。<br />

普 通 情 况 下 , 你 点 击 一 个 按 钮 , 去 后 台 执 行 一 个 process, 如 果 你 想 得 到 结 果 , 就 得 等 这 个 process 结 束 。 通<br />

常 , 可 以 使 用 异 步 执 行 回 调 来 解 决 这 个 问 题 。 现 在 ,backgroundworker 给 我 们 实 现 了 这 样 一 种 简 单 的 封 装 ,<br />

可 以 把 我 们 的 复 杂 任 务 交 给 新 的 线 程 去 处 理 , 然 后 继 续 UI 线 程 。 等 到 我 们 的 任 务 需 要 通 知 UI 做 什 么 事 情 的 时 候 ,<br />

可 以 report 一 下 , 在 其 事 件 里 就 可 以 直 接 使 用 UI 控 件 , 而 不 需 要 Control.Invoke 去 掉 用 之 。<br />

有 这 样 一 个 应 用 : 客 户 需 要 把 大 量 数 据 ( 需 要 执 行 3 天 )copy 到 另 外 一 个 机 器 , 中 间 想 能 看 到 有 多 少 数 据 被 复<br />

制 / 失 败 等 ( 实 时 报 道 )。<br />

在 这 个 例 子 里 面 , 我 们 的 界 面 可 能 非 常 简 单 : 一 个 开 始 按 钮 , 一 个 结 束 按 钮 , 一 个 richtextBox 来 显 示 运 行 记 录 。<br />

但 是 后 台 执 行 可 能 就 会 比 较 棘 手 。 如 果 简 单 的 执 行 , 并 且 报 告 , 那 么 整 个 界 面 将 失 去 响 应 ( 都 在 同 一 个 线 程 里 面 ,<br />

造 成 忙 碌 )。 这 时 候 , 可 以 使 用 这 个 backgroundworker 了 。 它 可 以 在 后 台 执 行 , 并 且 报 告 给 界 面 实 时 信 息 , 界<br />

面 不 会 失 去 响 应 。<br />

先 介 绍 一 下 backgroundworker 的 几 个 属 性 / 方 法<br />

.WorkerReportsProgress: 是 否 可 以 向 外 报 告 进 度 。<br />

.WorkerSupportsCancellation : 是 否 可 以 暂 停 任 务<br />

. CancellationPending: 是 否 正 在 暂 停 中<br />

. RunWorkerAsync() : 开 始 执 行 任 务 。 触 发 DoWork 事 件<br />

. ReportProgress(int percentPrgress,object userState) : 向 外 报 告 进 度 。 触 发 ProgressChanged<br />

事 件 . 其 中 , 参 数 可 以 在 ProgressChangedEventArgs(worker_ProgressChanged(object sender,<br />

ProgressChangedEventArgs e)) 中 得 到<br />

. CancelAsync() : 取 消 ( 暂 停 ) 执 行 。<br />

事 件<br />

worker.DoWork += new DoWorkEventHandler(worker_DoWork);// 执 行 任 务<br />

worker.RunWorkerCompleted += new


RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);// 任 务 结 束 时<br />

worker.ProgressChanged += new<br />

ProgressChangedEventHandler(worker_ProgressChanged)// 报 告 状 态<br />

按 照 上 边 的 资 料 , 我 们 这 个 应 用 就 可 以 这 样 处 理 之<br />

formDisplay 是 用 于 显 示 实 时 状 态 的 窗 口 。 有 DisplyMessage 方 法 来 显 示 信 息 到 界 面<br />

在 Hanlder 类 ( 处 理 文 件 copy 的 ) 里 面 :<br />

static void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)<br />

{<br />

//show the message on windows<br />

formDisplay.DisplyMessage(“copy”, e.UserState.ToString());//show message.<br />

}<br />

e)<br />

static void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs<br />

{<br />

string msg = "JOB copy : have been completed";<br />

formDisplay.DisplyMessage(msg);//show message<br />

}<br />

static void worker_DoWork(object sender, DoWorkEventArgs e)<br />

}<br />

{<br />

}<br />

for(…)<br />

{<br />

//copying<br />

(sender as BackgroundWorker). ReportProgress(0,”xxx complete”);//report<br />

这 样 构 造 的 程 序 里 面 , 才 不 会 出 现 UI 失 去 响 应 。<br />

当 然 , 通 过 写 自 己 的 异 步 处 理 也 可 以 实 现 , 功 能 更 强 大 。 只 不 过 这 个 用 起 来 更 简 单 。<br />

至 于 backgroundworker 是 怎 么 实 现 的 呢 ? 这 里 有 人 已 经 做 出 了 一 些 解 答 :<br />

http://blog.joycode.com/sunmast/archive/2006/03/02/about_system_componentmodel_asynco<br />

peration.aspx


在 UI 上 使 用 BackgroundWorker<br />

凡 是 WinForm 的 应 用 程 序 , 如 果 他 执 行 了 一 个 的 非 常 冗 长 的 处 理 操 作 ( 比 如 文 件 查 询 ), 它 在 执 行 时 会 锁 定<br />

用 户 界 面 , 虽 然 主 活 动 窗 口 一 直 在 运 行 , 但 用 户 无 法 与 程 序 交 互 , 无 法 移 动 窗 体 或 改 变 窗 体 大 小 , 所 以 用 户 感<br />

觉 很 不 爽 。 如 何 做 才 能 使 得 这 个 程 序 有 响 应 。 答 案 就 是 在 后 台 线 程 中 执 行 这 个 操 作 。<br />

在 这 里 已 经 有 了 多 种 方 法 来 做 这 个 事 情 :<br />

( 一 ) 委 托 异 步 调 用<br />

将 具 体 耗 时 的 操 作 作 为 一 个 委 托 , 并 用 BeginInvoke 来 异 步 执 行 这 个 委 托 (Invoke 是 同 步 调 用 ), 并 且 可 以<br />

为 这 个 操 作 传 入 参 数 并 且 通 过 EndInvoke 方 法 获 得 返 回 返 回 值 。<br />

( 二 ) 使 用 ThreadPool<br />

新 建 .net FrameWork 中 自 带 的 WaitCallback 委 托 , 然 后 放 到 线 程 池 中 运 行<br />

ThreadPool.QueueUserWorkItem( callback ); 根 据 WaitCallback 委 托 的 定 义 , 可 以 传 入 一 个 object 类<br />

型 的 参 数 。<br />

但 是 不 能 精 确 的 控 制 线 程 池 中 的 线 程 。<br />

( 三 ) 使 用 Thread<br />

和 ThreadPool 相 比 , 使 用 Thread 的 开 销 会 比 较 大 。 但 是 它 有 它 的 优 势 , 使 用 Thread 类 可 以 显 式 管<br />

理 线 程 。 只 要 有 可 能 , 就 应 该 使 用 ThreadPool 类 来 创 建 线 程 。 然 而 , 在 一 些 情 况 下 , 您 还 是 需 要 创 建 并 管 理<br />

您 自 己 的 线 程 , 而 不 是 使 用 ThreadPool 类 。 在 .net 2.0 中 , 提 供 了 一 个 新 的 委 托<br />

ParameterizedThreadStart 支 持 启 动 一 个 线 程 并 传 入 参 数 , 这 是 对 原 来 的 ThreadStart 委 托 的 改 进 。<br />

说 了 这 么 多 还 没 有 说 到 今 天 的 主 角 BackgroundWorker, 他 也 是 一 个 在 2.0 中 新 增 的 类 , 可 以 用 于 启 动 后 台<br />

线 程 , 并 在 后 台 计 算 结 束 后 调 用 主 线 程 的 方 法 . 可 以 看 出 同 样 的 功 能 使 用 委 托 的 异 步 调 用 也 可 以 实 现 , 只 是 使 用<br />

BackgroundWorker 的 话 会 更 加 的 简 便 快 捷 , 可 以 节 省 开 发 时 间 , 并 把 你 从 创 建 自 己 的 委 托 以 及 对 它 们 的 调 用<br />

中 解 救 出 来 。 真 是 这 样 的 吗 看 看 下 面 这 个 例 子 。 其 实 我 也 是 从 101Samples 中 看 到 的 例 子 。<br />

先 看 看 BackgroundWorker 中 的 主 要 概 念 。<br />

第 一 : 主 要 的 事 件 及 参 数 。<br />

DoWork—— 当 执 行 BackgroundWorker.RunWorkerAsync 方 法 时 会 触 发 该 事 件 , 并 且 传 递<br />

DoWorkEventArgs 参 数 ;<br />

ProgressChanged—— 操 作 处 理 中 获 得 的 处 理 状 态 变 化 , 通 过<br />

BackgroundWorker.ReportProgress(int) 方 法 触 发 该 事 件 , 并 且 传 递 ProgressChangedEventArgs, 其 中 包<br />

含 了 处 理 的 百 分 比 ;<br />

RunWorkerCompleted—— 异 步 操 作 完 成 后 会 触 发 该 事 件 , 当 然 如 果 需 要 在 操 作 过 程 中 结 束 可 以 执 行<br />

BackgroundWorker.CancelAsync 方 法 要 求 异 步 调 用 中 止 , 并 且 在 异 步 委 托 操 作 中 检 测<br />

BackgroundWorker.CancellationPending 属 性 如 果 为 true 的 话 , 跳 出 异 步 调 用 , 同 时 将<br />

DoWorkEventArgs.Cancel 属 性 设 为 true, 这 样 当 退 出 异 步 调 用 的 时 候 , 可 以 让 处 理 RunWorkerCompleted<br />

事 件 的 函 数 知 道 是 正 常 退 出 还 是 中 途 退 出 。<br />

第 二 : 主 要 的 方 法 。<br />

BackgroundWorker.RunWorkerAsync——<br />

“ 起 动 ” 异 步 调 用 的 方 法 有 两 次 重 载 RunWorkerAsync(),RunWorkerAsync(object argument),<br />

第 二 个 重 载 提 供 了 一 个 参 数 , 可 以 供 异 步 调 用 使 用 。( 如 果 有 多 个 参 数 要 传 递 怎 么 办 , 使 用 一 个 类 来 传 递 他 们 吧 )。<br />

调 用 该 方 法 后 会 触 发 DoWork 事 件 , 并 且 为 处 理 DoWork 事 件 的 函 数 DoWorkEventArg 事 件 参 数 , 其 中 包 含


了 RunWorkerAsync 传 递 的 参 数 。 在 相 应 DoWork 的 处 理 函 数 中 就 可 以 做 具 体 的 复 杂 操 作 。<br />

BackgroundWorker.ReportProgress——<br />

有 时 候 需 要 在 一 个 冗 长 的 操 作 中 向 用 户 不 断 反 馈 进 度 , 这 样 的 话 就 可 以 调 用 的 ReportProgress(int<br />

percent), 在 调 用 ReportProgress 方 法 时 , 触 发 ProgressChanged 事 件 。 提 供 一 个 在 0 到 100 之 间 的 整<br />

数 , 它 表 示 后 台 活 动 已 完 成 的 百 分 比 。 你 也 可 能 提 供 任 何 对 象 作 为 第 二 个 参 数 , 允 许 你 给 事 件 处 理 程 序 传 递 状<br />

态 信 息 。 作 为 传 递 到 此 过 程 的 ProgressChangedEventArgs 参 数 属 性 , 百 分 比 和 你 自 己 的 对 象 ( 如 果 提 供 的<br />

话 ) 均 要 被 传 递 到 ProgressChanged 事 件 处 理 程 序 。 这 些 属 性 被 分 别 命 名 为 ProgressPercentage 和<br />

UserState, 并 且 你 的 事 件 处 理 程 序 可 以 以 任 何 需 要 的 方 式 使 用 它 们 。( 注 意 : 只 有 在<br />

BackgroundWorker.WorkerReportsProgress 属 性 被 设 置 为 true 该 方 法 才 可 用 )。<br />

BackgroundWorker.CancelAsync——<br />

但 需 要 退 出 异 步 调 用 的 时 候 , 就 调 用 的 这 个 方 法 。 但 是 样 还 不 够 , 因 为 它 仅 仅 是 将<br />

BackgroudWorker.CancellationPending 属 性 设 置 为 true。 你 需 要 在 具 体 的 异 步 调 用 处 理 的 时 候 , 不 断 检 查<br />

BackgroudWorker.CancellationPending 是 否 为 true, 如 果 是 真 的 话 就 退 出 。( 注 意 : 只 有 在<br />

BackgroundWorker.WorkerSupportsCancellation 属 性 被 设 置 为 true 该 方 法 才 可 用 )。<br />

贴 出 一 段 101Samples 里 面 的 代 码 , 看 一 下 就 明 白 了 :<br />

public partial class MainForm : Form<br />

{<br />

private System.ComponentModel.BackgroundWorker backgroundCalculator;<br />

public MainForm()<br />

{<br />

InitializeComponent();<br />

backgroundCalculator = new BackgroundWorker();<br />

backgroundCalculator.WorkerReportsProgress = true;<br />

backgroundCalculator.WorkerSupportsCancellation = true;<br />

backgroundCalculator.DoWork +=<br />

new<br />

DoWorkEventHandler(backgroundCalculator_DoWork);<br />

backgroundCalculator.ProgressChanged +=<br />

new<br />

ProgressChangedEventHandler(backgroundCalculator_ProgressChanged);<br />

backgroundCalculator.RunWorkerCompleted +=<br />

new<br />

RunWorkerCompletedEventHandler(backgroundCalculator_RunWorkerCompleted);<br />

updateStatus(String.Empty);<br />

}<br />

private int getNextPrimeAsync(int start, BackgroundWorker worker, DoWorkEventArgs e)<br />

{<br />

int percentComplete = 0;<br />

start++;<br />

while (!isPrime(start))<br />

{<br />

// Check for cancellation<br />

if (worker.CancellationPending)<br />

{


e.Cancel = true;<br />

break;<br />

}<br />

else<br />

{<br />

start++;<br />

percentComplete++;<br />

worker.ReportProgress(percentComplete % 100);<br />

}<br />

}<br />

return start;<br />

}<br />

void backgroundCalculator_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs<br />

e)<br />

{<br />

if (e.Cancelled)<br />

{<br />

updateStatus("Cancelled.");<br />

}<br />

else if (e.Error != null)<br />

{<br />

reportError(e.Error);<br />

}<br />

else<br />

{<br />

reportPrime((int)e.Result);<br />

}<br />

calcProgress.Value = 0;<br />

}<br />

void backgroundCalculator_ProgressChanged(object sender, ProgressChangedEventArgs e)<br />

{<br />

updateProgress(e.ProgressPercentage);<br />

}<br />

void backgroundCalculator_DoWork(object sender, DoWorkEventArgs e)<br />

{<br />

int start = (int) e.Argument;<br />

e.Result = getNextPrimeAsync(start, (BackgroundWorker)sender, e);<br />

}<br />

private void nextPrimeAsyncButton_Click(object sender, EventArgs e)<br />

{<br />

updateStatus("Calculating...");<br />

int start;<br />

Int32.TryParse(textBoxPrime.Text, out start);<br />

if (start == 0)


{<br />

reportError("The number must be a valid integer");<br />

}<br />

else<br />

{<br />

// Kick off the background worker process<br />

backgroundCalculator.RunWorkerAsync(int.Parse(textBoxPrime.Text));<br />

}<br />

}<br />

private void cancelButton_Click(object sender, EventArgs e)<br />

{<br />

if (backgroundCalculator.IsBusy)<br />

{<br />

updateStatus("Cancelling...");<br />

backgroundCalculator.CancelAsync();<br />

}<br />

}<br />

// Update the Status label<br />

private void updateStatus(string status)<br />

{<br />

calcStatus.Text = status;<br />

}<br />

// Indicate progress using progress bar<br />

private void updateProgress(int percentComplete)<br />

{<br />

calcProgress.Value = percentComplete;<br />

}<br />

}<br />

BackgroundWorker 创 建 自 己 的 委 托 并 调 用 这 个 窗 体 的 Invoke 方 法 来 运 行 它 ,BackgroundWorker<br />

组 件 以 一 种 优 雅 的 方 式 来 处 理 这 个 线 程 转 换 。BackgroundWorker 组 件 允 许 你 从 后 台 线 程 中 调 用 它 的<br />

ReportProgress 方 法 , 该 方 法 触 发 其 ProgressChanged 事 件 处 理 例 程 返 回 到 窗 体 的 线 程 中 。 你 不 必 使 用<br />

delegate/Invoke 方 法 自 己 处 理 这 个 线 程 转 换 , 而 是 调 用 ReportProgress, 其 余 的 事 情 交 给 组 件 来 做 。


DotNet 中 异 步 编 程 的 简 单 应 用 - shenba - 博 客 园<br />

DotNet 中 异 步 编 程 的 简 单 应 用<br />

这 里 说 的 异 步 编 程 并 不 是 AJAX 等 的 Web 异 步 编 程 , 而 仅 仅 是 DotNet 中 多 线 程 的 异 步 编 程 . 这 种 多 线 程 的 异 步<br />

编 程 主 要 用 来 解 决 某 些 受 计 算 操 作 影 响 而 引 起 主 线 程 阻 塞 的 问 题 . 让 程 序 ( 主 要 是 窗 体 应 用 程 序 ) 看 跑 得 更 流 畅 . 在<br />

dotnet 的 CLR 以 及 API 方 法 中 有 简 单 易 用 的 方 法 供 我 们 实 现 异 步 编 程 , 并 且 都 有 相 似 的 调 用 方 法 , 诸 如<br />

BeginXXX,EndXXX,IAsyncResult 对 象 , 同 时 也 都 涉 及 到 回 调 , 委 托 等 操 作 . 下 面 是 一 些 简 单 的 应 用<br />

1. 异 步 的 IO 操 作 , 基 本 上 就 是 按 参 数 传 递<br />

异 步 IO<br />

1// 值 得 注 意 的 是 最 好 给 定 FileOptions.Asynchronous, 相 对 效 率 会 高 些<br />

2 FileStream fs = new FileStream("E:\\test.txt", FileMode.Open,<br />

FileAccess.Read, FileShare.Read, 1024, FileOptions.Asynchronous);<br />

3 byte[] data = new byte[(int)fs.Length];<br />

4<br />

5 // 开 始 读 取<br />

6 fs.BeginRead(data, 0, data.Length, delegate (IAsyncResult ar)<br />

7 {<br />

8 // 这 里 是 读 取 结 束 之 后 回 调 的 委 托 方 法 内 容<br />

9 fs.EndRead(ar);<br />

10 fs.Close();<br />

11<br />

12 string content = Encoding.Default.GetString(data);<br />

13 Console.WriteLine(content);<br />

14 }, fs);<br />

2. 读 取 数 据 库 内 容 的 异 步 操 作 , 这 里 的 代 码 是 用 在 窗 体 程 序 中 , 涉 及 到 一 个 跨 线 程 改 变 窗 体 控 件 的 问 题 , 但 是 在 窗<br />

体 里 , 只 有 通 过 窗 体 的 主 线 程 来 改 变 控 件 的 行 为 . 如 下 异 步 读 取 数 据 , 读 取 数 据 的 操 作 是 在 另 外 一 个 线 程 中 , 因 此 试<br />

图 在 这 个 线 程 中 操 作 窗 体 控 件 是 徒 劳 的 , 还 会 出 异 常 . 所 以 只 能 通 过 窗 体 的 invoke 方 法 , 调 用 一 个 委 托 , 这 样 才 能<br />

实 现 . 当 然 这 里 只 是 为 了 提 出 这 个 问 题 , 其 他 的 解 决 的 方 法 还 是 有 的 , 比 如 效 率 更 低 的 轮 询 方 法 .<br />

数 据 访 问<br />

1// 这 个 委 托 用 于 在 窗 体 的 主 线 程 中 调 用<br />

2 private delegate void FillGridHandler(SqlDataReader reader);<br />

3 private void AsnDataAccess()<br />

4 {<br />

5 // 对 于 异 步 操 作 的 数 据 访 问 , 连 接 字 符 串 里 必 须 设 置 Async=True<br />

6 string conStr =<br />

ConfigurationManager.ConnectionStrings["NorthWind"].ConnectionString;<br />

7 SqlConnection con = new SqlConnection(conStr);<br />

8 SqlCommand command = new SqlCommand("select [CompanyName] from<br />

[Suppliers]", con);<br />

9<br />

10 con.Open();


11 command.BeginExecuteReader(delegate(IAsyncResult ar)<br />

12 {<br />

13 SqlDataReader reader = command.EndExecuteReader(ar);<br />

14<br />

15 // 对 于 窗 体 应 用 程 序 的 GridView 的 绑 定<br />

16 FillGridHandler fillGridHandler = delegate(SqlDataReader<br />

reader1)<br />

17 {<br />

18 DataTable dt = new DataTable();<br />

19 dt.Load(reader1);<br />

20 reader.Close();<br />

21 dataGridView1.DataSource = dt;<br />

22 };<br />

23<br />

24 // 用 窗 体 自 身 的 线 程 去 触 发 委 托 方 法 才 能 改 变 窗 体 里 控 件 属 性<br />

25 this.Invoke(fillGridHandler, reader);<br />

26 }, command, System.Data.CommandBehavior.CloseConnection);<br />

27 }<br />

3. 异 步 触 发 委 托 的 方 法 , 异 步 编 程 离 不 开 委 托 , 其 本 身 也 就 是 调 用 了 委 托 的 异 步 方 法 , 其 内 部 就 必 定 有 一 个 委 托 对<br />

象<br />

委 托 异 步<br />

1 delegate int CalSumHandler(int number);<br />

2 private static void DelegateAsyn()<br />

3 {<br />

4 CalSumHandler handler = delegate(int number)<br />

5 {<br />

6 int sum = 0;<br />

7 for (int i = 0; i < number; i++)<br />

8 {<br />

9 sum += i;<br />

10 }<br />

11 return sum;<br />

12 };<br />

13<br />

14 int n = 10;<br />

15 handler.BeginInvoke(n, delegate(IAsyncResult ar)<br />

16 {<br />

17 int res = handler.EndInvoke(ar);<br />

18 Console.WriteLine("result from asyndelegate,sum = {0}", res);<br />

19 }, n);<br />

20 }<br />

posted on 2007-10-01 11:12 神 八 阅 读 (102) 评 论 (0) 编 辑 收 藏


Delegate 比 较 全 面 的 例 子 ( 原 创 ) - 享 受 代 码 , 享 受 人 生 - 博 客 园<br />

享 受 代 码 , 享 受 人 生<br />

SOA is an integration solution. SOA is message oriented first.<br />

The Key character of SOA is loosely coupled. SOA is enriched by<br />

creating composite apps.<br />

将 Delegate 理 解 为 接 口 , 只 有 一 个 方 法 的 接 口 , 这 样 最 容 易 理 解 。 这 个 方 法 只 有 声 明 , 没 有 实 现 , 实 现<br />

在 别 的 类 。( 实 际 上 应 该 把 它 看 作 函 数 指 针 , 不 过 接 口 更 容 易 理 解 些 。)<br />

在 你 的 类 中 有 一 个 Delegate 就 相 当 于 有 一 个 接 口 。 通 过 这 个 接 口 你 可 以 调 用 一 个 方 法 , 而 这 个 方 法 在 别<br />

的 类 定 义 , 由 别 的 类 来 干 。<br />

为 了 说 的 形 象 一 点 , 举 个 例 子 :<br />

学 生 考 试 完 后 成 绩 出 来 了 , 考 的 好 了 老 师 要 表 扬 , 考 的 不 好 了 老 师 要 批 评 。<br />

使 用 接 口 的 方 法 :<br />

using System;<br />

public class Student<br />

{<br />

private IAdviser adviser;<br />

public void SetAdviser(IAdviser iadviser)<br />

{<br />

adviser = iadviser;<br />

}<br />

private int score;<br />

public void SetScore(int value)<br />

{<br />

if (value > 100 || value < 0)<br />

{<br />

Console.Out.WriteLine(" 分 数 不 对 ");<br />

}<br />

else<br />

{<br />

score = value;<br />

if (adviser != null)<br />

{<br />

string result = adviser.Advise(score);<br />

Console.Out.WriteLine(" 学 生 收 到 老 师 返 回 的 结 果 \t"+result);<br />

}<br />

}<br />

}


}<br />

public interface IAdviser<br />

{<br />

string Advise(int score);<br />

}<br />

public class Teacher : IAdviser<br />

{<br />

public string Advise(int score)<br />

{<br />

if (score < 60)<br />

{<br />

Console.Out.WriteLine(score+" 老 师 说 加 油 ");<br />

return " 不 及 格 ";<br />

}<br />

else<br />

{<br />

Console.Out.WriteLine(score+" 老 师 说 不 错 ");<br />

return " 及 格 ";<br />

}<br />

}<br />

}<br />

class MainClass<br />

{<br />

[STAThread]<br />

private static void Main(string[] args)<br />

{<br />

IAdviser teacher = new Teacher();<br />

Student s = new Student();<br />

s.SetAdviser(teacher);<br />

Console.Out.WriteLine(" 学 生 得 到 50 分 ");<br />

s.SetScore(50);<br />

Console.Out.WriteLine("\n 学 生 得 到 75 分 ");<br />

s.SetScore(75);<br />

}<br />

}<br />

Console.ReadLine();<br />

使 用 Delegate 的 方 法 :


using System;<br />

using System.Threading;<br />

public class Student<br />

{<br />

private int score;<br />

public void SetScore(int value)<br />

{<br />

if (value > 100 || value < 0)<br />

{<br />

Console.Out.WriteLine(" 分 数 不 对 ");<br />

}<br />

else<br />

{<br />

score = value;<br />

if (AdviseDelegateInstance!= null)<br />

{<br />

string result=AdviseDelegateInstance(score);<br />

Console.Out.WriteLine(" 学 生 收 到 老 师 返 回 的 结 果 \t"+result);<br />

}<br />

}<br />

}<br />

public delegate string AdviseDelegate(int score);<br />

}<br />

public AdviseDelegate AdviseDelegateInstance;<br />

public class Teacher<br />

{<br />

public string Advise(int score)<br />

{<br />

if(score


}<br />

}<br />

class MainClass<br />

{<br />

[STAThread]<br />

static void Main(string[] args)<br />

{<br />

Teacher teacher=new Teacher();<br />

Student s=new Student();<br />

s.AdviseDelegateInstance=new<br />

Student.AdviseDelegate(teacher.Advise);<br />

Console.Out.WriteLine(" 学 生 得 到 50 分 ");<br />

s.SetScore(50);<br />

Console.Out.WriteLine("\n 学 生 得 到 75 分 ");<br />

s.SetScore(75);<br />

}<br />

}<br />

Console.ReadLine();<br />

如 果 老 师 很 忙 不 能 及 时 回 复 怎 么 办 ? 比 如 这 样 :<br />

public class Teacher<br />

{<br />

public string Advise(int score)<br />

{<br />

Thread.Sleep(3000);<br />

if(score


Interface 的 解 决 办 法 :<br />

public void SetScore(int value)<br />

{<br />

if (value > 100 || value < 0)<br />

{<br />

Console.Out.WriteLine(" 分 数 不 对 ");<br />

}<br />

else<br />

{<br />

score = value;<br />

if (adviser != null)<br />

{<br />

Thread.adviserThread=new Thread(new<br />

ThreadStart(adviser.Advise()));<br />

adviserThread.Start();<br />

}<br />

}<br />

}<br />

但 是 它 不 能 使 用 带 参 数 的 函 数 , 怎 么 办 ?( 谁 知 道 方 法 请 指 教 )<br />

.Net2.0 提 供 了 新 的 方 法 ParameterizedThreadStart<br />

用 Delegate 解 决 ( 异 步 调 用 ):<br />

public void SetScore(int value)<br />

{<br />

if (value > 100 || value < 0)<br />

{<br />

Console.Out.WriteLine(" 分 数 不 对 ");<br />

}<br />

else<br />

{<br />

score = value;<br />

if (AdviseDelegateInstance!= null)<br />

{<br />

AdviseDelegateInstance.BeginInvoke(score,null,null);<br />

}<br />

}<br />

}<br />

不 过 这 样 我 们 失 去 了 老 师 的 返 回 结 果 , 不 知 道 有 没 有 及 格 了 。<br />

采 用 轮 讯 的 方 法 去 获 得 结 果 :<br />

public void SetScore(int value)<br />

{<br />

if (value > 100 || value < 0)


{<br />

}<br />

else<br />

{<br />

Console.Out.WriteLine(" 分 数 不 对 ");<br />

score = value;<br />

if (AdviseDelegateInstance!= null)<br />

{<br />

IAsyncResult res =<br />

AdviseDelegateInstance.BeginInvoke(score,null, null);<br />

while( !res.IsCompleted )<br />

System.Threading.Thread.Sleep(1);<br />

string result =<br />

AdviseDelegateInstance.EndInvoke(res);<br />

Console.Out.WriteLine(" 学 生 收 到 老 师 返 回 的 结 果 \t"+result);<br />

}<br />

}<br />

}<br />

不 过 这 样 主 线 程 又 被 阻 塞 了 , 采 用 回 调 的 方 式 : ( 注 : 接 口 也 可 以 采 用 回 调 的 方 式 获 得 返 回 值 )<br />

public void SetScore(int value)<br />

{<br />

if (value > 100 || value < 0)<br />

{<br />

Console.Out.WriteLine(" 分 数 不 对 ");<br />

}<br />

else<br />

{<br />

score = value;<br />

if (AdviseDelegateInstance!= null)<br />

{<br />

IAsyncResult res =<br />

AdviseDelegateInstance.BeginInvoke(score, new<br />

System.AsyncCallback(CallBackMethod), null);<br />

}<br />

}<br />

}<br />

private void CallBackMethod(IAsyncResult asyncResult)<br />

{


string result = AdviseDelegateInstance.EndInvoke(asyncResult);<br />

Console.Out.WriteLine(" 学 生 收 到 老 师 返 回 的 结 果 \t" + result);<br />

}<br />

这 样 就 比 较 得 到 了 一 个 比 较 好 的 解 决 方 案 了 。 我 们 再 来 看 看 BeginInvoke 的 第 四 个 参 数 是 干 吗 的 呢 ?<br />

public void SetScore(int value)<br />

{<br />

if (value > 100 || value < 0)<br />

{<br />

Console.Out.WriteLine(" 分 数 不 对 ");<br />

}<br />

else<br />

{<br />

score = value;<br />

if (AdviseDelegateInstance!= null)<br />

{<br />

AdviseDelegateInstance.BeginInvoke(score, new<br />

System.AsyncCallback(CallBackMethod), "idior");<br />

}<br />

}<br />

}<br />

private void CallBackMethod(IAsyncResult asyncResult)<br />

{<br />

string result = AdviseDelegateInstance.EndInvoke(asyncResult);<br />

string stateObj=(string)asyncResult.AsyncState;<br />

Console.Out.WriteLine(" 学 生 {0} 收 到 老 师 返 回 的 结 果 \t" +<br />

result,stateObj.ToString());<br />

}<br />

哦 , 原 来 它 可 以 用 来 标 记 调 用 者 的 一 些 信 息 。( 这 里 采 取 的 是 硬 编 码 的 方 式 , 你 可 以 把 它 改 为 学 生 的 id 之<br />

类 的 信 息 )。<br />

总 结 :Delegate 类 似 与 Interface 但 是 功 能 更 加 强 大 和 灵 活 , 它 甚 至 还 可 以 绑 定 到 Static 方 法 只 要 函 数<br />

签 名 一 致 , 而 且 由 于 += 操 作 符 的 功 能 , 实 现 多 播 也 是 极 为 方 便 ( 即 Observer 模 式 ), 在 此 不 再 举 例 。<br />

( 补 充 : 多 播 的 时 候 改 一 下 SetScore 函 数 )<br />

public void SetScore(int value)<br />

{<br />

if (value > 100 || value < 0)<br />

{


Console.Out.WriteLine(" 分 数 不 对 ");<br />

}<br />

else<br />

{<br />

score = value;<br />

if (AdviseDelegateInstance!= null)<br />

{<br />

foreach( AdviseDelegate ad in<br />

AdviseDelegateInstance.GetInvocationList())<br />

{<br />

ad.BeginInvoke(score, new<br />

System.AsyncCallback(CallBackMethod), "idior");<br />

}<br />

}<br />

}<br />

}<br />

程 模 型 。<br />

本 文 没 什 么 新 的 内 容 , 就 是 自 己 练 一 下 手 , 写 个 总 结 材 料 , 希 望 对 大 家 有 帮 助 。.net2.0 提 供 了 更 好 的 线<br />

完 整 源 代 码 如 下 :<br />

using System;<br />

using System.Threading;<br />

public class Student<br />

{<br />

private int score;<br />

public void SetScore(int value)<br />

{<br />

if (value > 100 || value < 0)<br />

{<br />

Console.Out.WriteLine(" 分 数 不 对 ");<br />

}<br />

else<br />

{<br />

score = value;<br />

if (AdviseDelegateInstance!= null)<br />

{<br />

AdviseDelegateInstance.BeginInvoke(score, new<br />

System.AsyncCallback(CallBackMethod), "idior");<br />

}


}<br />

}<br />

private void CallBackMethod(IAsyncResult asyncResult)<br />

{<br />

string result = AdviseDelegateInstance.EndInvoke(asyncResult);<br />

string stateObj=(string)asyncResult.AsyncState;<br />

}<br />

Console.Out.WriteLine(" 学 生 {0} 收 到 老 师 返 回 的 结 果 \t" + result,stateObj);<br />

}<br />

public delegate string AdviseDelegate(int score);<br />

public AdviseDelegate AdviseDelegateInstance;<br />

public class Teacher<br />

{<br />

public string Advise(int score)<br />

{<br />

Thread.Sleep(3000);<br />

if (score < 60)<br />

{<br />

Console.Out.WriteLine(score + " 老 师 说 加 油 ");<br />

return " 不 及 格 ";<br />

}<br />

else<br />

{<br />

Console.Out.WriteLine(score + " 老 师 说 不 错 ");<br />

return " 及 格 ";<br />

}<br />

}<br />

}<br />

class MainClass<br />

{<br />

[STAThread]<br />

private static void Main(string[] args)<br />

{<br />

Teacher teacher = new Teacher();<br />

Student s = new Student();<br />

s.AdviseDelegateInstance= new<br />

Student.AdviseDelegate(teacher.Advise);<br />

Console.Out.WriteLine(" 学 生 得 到 50 分 ");<br />

s.SetScore(50);


}<br />

}<br />

Console.Out.WriteLine("\n 学 生 得 到 75 分 ");<br />

s.SetScore(75);<br />

Console.ReadLine();<br />

参 考 资 料 : .NET Delegates: A C# Bedtime Story<br />

Feedback<br />

# re: Delegate 比 较 全 面 的 例 子 ( 原 创 ) 回 复 更 多 评 论<br />

2005-02-02 10:57 by KingofSC<br />

不 错 啊 , 居 然 还 说 到 多 线 程 去 了<br />

# re: Delegate 比 较 全 面 的 例 子 ( 原 创 ) 回 复 更 多 评 论<br />

2005-02-02 11:27 by idior<br />

@KingofSC<br />

发 现 你 总 是 潜 水 哦 :P<br />

哪 天 看 看 你 的 大 作 啊 ?<br />

# re: Delegate 比 较 全 面 的 例 子 ( 原 创 ) 回 复 更 多 评 论<br />

2005-02-02 14:05 by 吕 震 宇<br />

不 错 ! 很 全 面 。 就 是 “Advise” 出 现 得 太 多 了 , 有 时 候 分 不 清 是 Advise 方 法 还 是 Advise 委 派 了 :)<br />

# re: Delegate 比 较 全 面 的 例 子 ( 原 创 ) 回 复 更 多 评 论<br />

2005-02-02 15:22 by idior<br />

@ 吕 震 宇<br />

不 好 意 思 , 是 有 点 让 人 看 不 懂 , 已 修 改 . 谢 谢 指 正 .<br />

# re: Delegate 比 较 全 面 的 例 子 ( 原 创 ) 回 复 更 多 评 论<br />

2005-03-11 00:56 by douhao_lale<br />

很 好 啊 , 谢 谢<br />

# re: Delegate 比 较 全 面 的 例 子 ( 原 创 ) 回 复 更 多 评 论<br />

2005-07-25 15:46 by Boler<br />

开 始 看 的 挺 明 白 , 后 来 太 复 杂 了 , 看 不 懂 了 , 放 弃 了 ~<br />

# re: Delegate 比 较 全 面 的 例 子 ( 原 创 ) 回 复 更 多 评 论<br />

2005-07-25 20:46 by idior<br />

需 要 用 到 多 线 程 的 时 候 再 来 看 看 吧<br />

# re: Delegate 比 较 全 面 的 例 子 ( 原 创 ) 回 复 更 多 评 论<br />

2005-07-26 08:17 by yinh<br />

不 错 ,idior 你 写 的 文 章 我 都 非 常 感 兴 趣 。<br />

# re: Delegate 比 较 全 面 的 例 子 ( 原 创 ) 回 复 更 多 评 论<br />

2005-07-30 17:06 by HNHLS99<br />

上 面 的 多 线 程 的 例 子 比 较 透 彻 , 但 是 对 多 线 程 的 机 制 涉 及 的 很 少 啊 , 希 望 能 补 充 以 下 。 比 如 线 程 的 轮 询 ,<br />

线 程 的 开 始 与 结 束 , 异 步 的 调 用 的 同 步 等 等 。<br />

# re: Delegate 比 较 全 面 的 例 子 ( 原 创 ) 回 复 更 多 评 论<br />

2005-07-30 17:49 by idior<br />

呵 呵 别 忘 了 本 文 的 题 目 “Delegate 比 较 全 面 的 例 子 ”<br />

或 许 可 以 由 你 来 介 绍 多 线 程 啊 。<br />

# re: Delegate 比 较 全 面 的 例 子 ( 原 创 ) 回 复 更 多 评 论


2005-08-18 11:18 by 阿 新<br />

牛 , 呕 像<br />

# re: Delegate 比 较 全 面 的 例 子 ( 原 创 ) 回 复 更 多 评 论<br />

2005-11-22 10:45 by luyu<br />

前 面 看 的 不 错 , 越 看 越 混 乱 了 。<br />

# re: Delegate 比 较 全 面 的 例 子 ( 原 创 ) 回 复 更 多 评 论<br />

2005-12-14 17:05 by giogio<br />

呵 呵 , 其 实 写 到 “ 如 果 老 师 很 忙 不 能 及 时 回 复 怎 么 办 ? 比 如 这 样 :” 之 前 比 较 好 。<br />

后 面 就 和 多 线 程 结 合 的 更 紧 密 了 。<br />

我 觉 得 线 程 本 身 比 delegate 要 更 大 更 基 本 ,delegate 可 以 算 一 节 , 线 程 就 应 该 算 一 章 。<br />

类 似 于 讲 蒸 气 机 的 时 候 讲 到 一 半 开 始 用 量 子 力 学 解 释 …… 这 个 确 实 容 易 让 人 晕 ……<br />

# re: Delegate 比 较 全 面 的 例 子 ( 原 创 ) 回 复 更 多 评 论<br />

2005-12-19 09:41 by giogio<br />

“ 将 Delegate 理 解 为 接 口 , 只 有 一 个 方 法 的 接 口 , 这 样 最 容 易 理 解 。”<br />

这 么 说 不 好 吧 ……<br />

我 觉 得 这 俩 玩 意 不 是 一 回 事 啊 , 只 是 看 起 来 比 较 想 像 而 已 。<br />

# re: Delegate 比 较 全 面 的 例 子 ( 原 创 ) 回 复 更 多 评 论<br />

2005-12-19 22:16 by idior<br />

@ giogio<br />

记 住 这 句 话 , 或 许 有 一 天 你 会 突 然 觉 得 有 道 理 的 。<br />

这 是 delegate 的 本 质 , 不 过 需 要 你 对 面 向 对 象 有 一 定 的 理 解 。<br />

你 可 以 参 考 一 下 这 篇 文 章 。<br />

http://idior.cnblogs.com/archive/2005/02/03/101510.aspx<br />

http://linkcd.cnblogs.com/archive/2005/07/19/196087.html<br />

# re: Delegate 比 较 全 面 的 例 子 ( 原 创 ) 回 复 更 多 评 论<br />

2005-12-19 22:46 by giogio<br />

其 实 是 这 样 的 。<br />

老 师 讲 完 delegate 后 , 我 就 说 : 这 玩 意 和 interface 起 的 作 用 一 样 。<br />

老 师 就 说 : 从 表 面 上 看 是 这 样 的 , 但 是 两 者 从 根 本 上 不 同 。<br />

他 似 乎 很 像 让 我 牢 牢 记 住 这 是 两 种 东 西 , 强 调 这 两 个 不 能 混 淆 。<br />

老 师 还 让 我 准 备 一 下 , 用 大 约 15 分 钟 给 大 家 讲 delegate 和 event 呢 ……<br />

# re: Delegate 比 较 全 面 的 例 子 ( 原 创 ) 回 复 更 多 评 论<br />

2006-03-18 17:03 by qq:86562467<br />

拜 托 各 位 高 手 , 谁 哪 儿 有 关 于 Delegate Event WebService 方 面 的 例 子<br />

我 想 实 现 如 下 的 功 能


假 设 我 有 三 个 类 分 别 是 Class S , Class A , Class B<br />

现 在 想 用 代 理 Delegate 和 事 件 Event 实 现 类 A 和 类 B 的 通 信 功 能<br />

但 是 类 A 和 类 B 不 能 直 接 通 信 必 须 通 过 类 S 实 现<br />

类 S 就 是 起 一 个 中 转 或 服 务 器 的 作 用<br />

谁 哪 儿 有 这 样 的 例 子 拜 托 给 一 份 学 习 一 下<br />

谢 谢 了<br />

# re: Delegate 比 较 全 面 的 例 子 ( 原 创 ) 回 复 更 多 评 论<br />

2006-04-26 10:48 by anchky<br />

收 藏 了 !<br />

# re: Delegate 比 较 全 面 的 例 子 ( 原 创 ) 回 复 更 多 评 论<br />

2006-08-10 09:28 by 匿 名<br />

貌 似 老 大 很 习 惯 用 JAVA, 相 信 public void SetScore(int value)<br />

不 能 通 过 编 译 , 似 乎 s.AdviseDelegateInstance=new<br />

Student.AdviseDelegate(teacher.Advise) 也 是 不 能 通 过 编 译 的 , 因 为 你 没 有 实 现 Advise 的 get 方 法 。<br />

但 老 大 确 实 很 有 想 法 ,<br />

把 一 些 问 题 说 得 比 较 清 楚<br />

# re: Delegate 比 较 全 面 的 例 子 ( 原 创 ) 回 复 更 多 评 论<br />

2006-09-13 16:20 by lxinxuan<br />

Console.Out.WriteLine(" 学 生 {0} 收 到 老 师 返 回 的 结 果 \t" + result,stateObj); 改 为<br />

Console.Out.WriteLine(string.Format(" 学 生 {0} 收 到 老 师 返 回 的 结 果 \t" + result,<br />

stateObj));<br />

# re: Delegate 比 较 全 面 的 例 子 ( 原 创 ) 回 复 更 多 评 论<br />

2006-09-13 17:11 by lxinxuan<br />

@qq:86562467:<br />

我 知 道 以 下 是 没 有 满 足 你 的 要 求 , 所 以 , 我 请 求 idior 帮 忙 解 答 :<br />

public class A<br />

{<br />

public delegate void SetValueDelegate(string v);<br />

public SetValueDelegate SetValueInstance;<br />

public void SetValue(string v)<br />

{<br />

SetValueInstance(v);<br />

}<br />

}<br />

public class S<br />

{<br />

B b = new B();<br />

public void SetValue(string v)<br />

{<br />

b.SetValue(v);<br />

}


}<br />

public class B<br />

{<br />

public string bValue;<br />

public B()<br />

{<br />

bValue = "b";<br />

}<br />

public void SetValue(string v)<br />

{<br />

this.bValue = v;<br />

}<br />

public string GetValue()<br />

{<br />

return this.bValue;<br />

}<br />

}<br />

class MainClass<br />

{<br />

[STAThread]<br />

private static void Main(string[] args)<br />

{<br />

S s = new S();<br />

A a = new A();<br />

B b = new B();<br />

a.SetValueInstance = new A.SetValueDelegate(s.SetValue);<br />

a.SetValue("a");<br />

MessageBox.Show(b.GetValue());//<br />

}<br />

}<br />

# re: Delegate 比 较 全 面 的 例 子 ( 原 创 ) 回 复 更 多 评 论<br />

2006-09-19 00:53 by huangyi_<br />

<br />

class Delegate(object):<br />

' 模 拟 .net 的 delegate'<br />

def __init__(self):<br />

self.handlers = []<br />

def __call__(self,*args,**kw):<br />

for h in self.handlers:


h(*args,**kw)<br />

def __iadd__(self,handler):<br />

self.handlers.append(handler)<br />

return self<br />

d = Delegate()<br />

def handler1(a,b):print a,b<br />

def handler2(a,b):print a+b<br />

d(1,2)<br />

<br />

也 许 这 个 可 以 帮 助 理 解 ? 呵 呵<br />

# re: Delegate 比 较 全 面 的 例 子 ( 原 创 ) 回 复 更 多 评 论<br />

2006-10-12 16:59 by wthorse<br />

但 是 它 不 能 使 用 带 参 数 的 函 数 , 怎 么 办 ?<br />

可 以 在 调 用 的 target 所 在 的 类 中 定 义 属 性 , 调 用 多 线 程 之 前 先 赋 值 。<br />

# re: Delegate 比 较 全 面 的 例 子 ( 原 创 ) 回 复 更 多 评 论<br />

2007-02-11 16:03 by 臭 石 头<br />

多 线 程 传 递 参 数 , 我 经 常 用 , 呵 呵 。<br />

员 。<br />

写 一 个 类 来 包 装 , 写 一 个 没 有 参 数 的 方 法 , 方 法 内 部 调 用 带 参 数 的 方 法 , 这 些 参 数 , 作 为 这 个 类 的 公 共 成<br />

声 明 这 个 类 的 一 个 实 例 , 然 后 …… 剩 下 的 不 说 了<br />

# 对 Delegate 的 理 解 [TrackBack] 回 复 更 多 评 论<br />

2007-03-20 18:06 by 9q<br />

下 面 这 篇 文 章 论 述 的 比 较 有 意 思 :Delegate 比 较 全 面 的 例 子 ( 原 创 ) 查 看 原 文<br />

System.ComponentModel.AsyncOperation 类 - 这 个 类 很 特 别 昨 天 在 尝 试 使 用<br />

System.ComponentModel.BackgroundWorker 时 , 发 现 这 个 类 的 行 为 和 我 预 料 的 大 不 一 样 , 可 以 说 是 惊 喜 。<br />

原 来 以 为 这 个 类 只 是 一 个 线 程 的 简 单 包 装 , 用 多 线 程 模 拟 了 异 步 调 用 而 已 ; 但 是 看 下 面 的 这 段 代 码 :<br />

Thread.CurrentThread.Name = "Main Thread";<br />

backgroundWorker1.RunWorkerAsync();<br />

...


private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)<br />

{<br />

int i = 0;<br />

while (i++ < 100)<br />

{<br />

backgroundWorker1.ReportProgress(i);<br />

Thread.Sleep(50);<br />

}<br />

}<br />

private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)<br />

{<br />

this.Text = e.ProgressPercentage + "% - " + Thread.CurrentThread.Name;<br />

}<br />

private void backgroundWorker1_RunWorkerCompleted(object sender,<br />

RunWorkerCompletedEventArgs e)<br />

{<br />

this.Text = "DONE - " + Thread.CurrentThread.Name;<br />

}<br />

毫 无 疑 问 ,_DoWork 方 法 是 运 行 在 另 一 个 不 同 线 程 之 上 的 ( 很 容 易 验 证 这 一 点 , 这 也 符 合 BackgroundWorker<br />

的 设 计 ), 而 这 个 方 法 又 调 用 了 backgroundWorker1.ReportProgress 方 法 , 触 发 了 ProgressChanged 事 件 。<br />

在 通 常 的 异 步 实 现 ,_ProgressChanged 方 法 应 该 运 行 于 事 件 触 发 者 相 同 的 线 程 中 ; 但 在 这 里 , 它 运 行 于 主 线<br />

程 ( 名 为 Main Thread 的 线 程 )。_RunWorkerCompleted 方 法 也 是 一 样 。<br />

在 我 看 来 , 这 个 行 为 非 常 特 别 , 实 际 上 它 也 非 常 有 用 。 这 样 _ProgressChanged 方 法 体 中 操 作 UI 控 件 的 代 码 都<br />

无 需 使 用 Control.Invoke 包 装 了 , 让 程 序 的 编 写 大 为 简 化 。 而 我 真 正 感 兴 趣 的 是 这 个 类 究 竟 是 怎 么 实 现 的 , 我<br />

用 Reflector 打 开 它 的 源 码 之 后 , 原 来 关 键 在 于 它 用 到 了 一 个 名 为 AsyncOperation 的 类<br />

(System.ComponentModel.AsyncOperation)。<br />

AsyncOperation 类 有 个 Post 方 法 , 可 以 用 来 把 一 个 委 托 ( 作 为 方 法 指 针 / 列 表 ) 提 交 给 另 一 个 线 程 执 行 。 继 续<br />

反 编 译 下 去 , 又 查 到 了 System.Threading.SynchronizationContext 类 。 不 过 具 体 怎 么 实 现 是 无 从 得 知 了 ,<br />

因 为 追 踪 到 最 后 , 停 在 了 一 个 [MethodImpl(MethodImplOptions.InternalCall)] 的 方 法 , 它 由 CLR 本 身 实 现 。<br />

( 我 个 人 猜 测 , 其 中 很 可 能 利 用 了 Windows API:Get/SetThreadContext, 和 结 构 体 CONTEXT, 改 掉 了 线<br />

程 上 下 文 。)<br />

退 一 步 说 , 它 怎 么 实 现 的 并 不 是 那 么 重 要 , 重 要 的 是 我 们 可 以 用 这 个 AsyncOperation 类 实 现 自 己 的<br />

BackgroundWorker。 这 里 是 我 写 的 和 上 面 代 码 基 本 等 价 的 实 现 :<br />

AsyncOperation asyncOperation;<br />

SendOrPostCallback progressReporter;<br />

Thread workerThread;


public MainForm()<br />

{<br />

InitializeComponent();<br />

}<br />

asyncOperation = AsyncOperationManager.CreateOperation(null);<br />

progressReporter = new SendOrPostCallback(ReportProgress);<br />

workerThread = new Thread(new ThreadStart(WorkOnWorkerThread));<br />

private void MainForm_Load(object sender, EventArgs e)<br />

{<br />

Thread.CurrentThread.Name = "Main Thread";<br />

workerThread.Name = "Worker Thread";<br />

workerThread.IsBackground = true;<br />

workerThread.Start();<br />

}<br />

void ReportProgress(object obj)<br />

{<br />

this.Text = obj.ToString() + "% - " + Thread.CurrentThread.Name;<br />

}<br />

void WorkOnWorkerThread()<br />

{<br />

int i = 0;<br />

while (i++ < 100)<br />

{<br />

asyncOperation.Post(progressReporter, i);<br />

Thread.Sleep(50);<br />

}<br />

}<br />

C# 事 件 编 程 - 五 年 - 博 客 园 wxy<br />

1. 定 义 一 个 " 代 表 "<br />

如 :<br />

public delegate void DisconnectedEventHandler(object<br />

sender,ClientEventArgs e);<br />

2. 定 义 事 件 参 数<br />

如 :<br />

public class ClientEventArgs : EventArgs<br />

{


public IPAddress IP<br />

{<br />

get { return ( (IPEndPoint)this.socket.RemoteEndPoint<br />

).Address; }<br />

}<br />

public int Port<br />

{<br />

get{return ((IPEndPoint)this.socket.RemoteEndPoint).Port;}<br />

}<br />

public ClientEventArgs(Socket clientManagerSocket)<br />

{<br />

this.socket = clientManagerSocket;<br />

}<br />

}<br />

3. 使 用 " 代 表 " 定 义 一 个 事 件<br />

public event DisconnectedEventHandler Disconnected;<br />

4. 触 发 事 件<br />

protected virtual void OnDisconnected(ClientEventArgs e)<br />

{<br />

if ( Disconnected != null )<br />

Disconnected(this , e);<br />

}<br />

this.OnDisconnected(new ClientEventArgs(this.socket));<br />

5. 使 用 事 件<br />

ClientManager newClientManager = new ClientManager(socket);<br />

newClientManager.Disconnected += new<br />

DisconnectedEventHandler(ClientDisconnected);<br />

6. 定 义 事 件 处 理 方 法<br />

void ClientDisconnected(object sender , ClientEventArgs e)<br />

{<br />

if ( this.RemoveClientManager(e.IP) )<br />

this.UpdateConsole("Disconnected." , e.IP , e.Port);<br />

}


关 于 .NET 异 步 调 用 的 初 步 总 结<br />

最 近 看 了 看 .NET 异 步 调 用 方 面 的 资 料 , 现 择 重 点 总 结 , 若 有 纰 漏 敬 请 指 正 。<br />

异 步 调 用 的 实 质 :<br />

异 步 调 用 通 过 委 托 将 所 需 调 用 的 方 法 置 于 一 个 新 线 程 上 运 行 , 从 而 能 够 使 一 个 可 能 需 要 较 长 时 间 的 任 务 在 后 台 执<br />

行 而 不 影 响 调 用 方 的 其 他 行 为 。<br />

异 步 调 用 的 实 现 :<br />

前 面 已 经 讲 道 , 异 步 调 用 通 过 委 托 实 现 。 委 托 支 持 同 步 和 异 步 调 用 。 在 同 步 调 用 中 , 一 个 委 托 的 实 例 可 记 录 多 个<br />

目 标 方 法 ; 在 异 步 调 用 中 , 一 个 委 托 实 例 中 有 且 只 能 包 含 一 个 目 标 方 法 。 异 步 调 用 使 用 委 托 实 例 的 BeginInvoke<br />

方 法 和 EndInvoke 方 法 分 别 开 始 调 用 和 检 索 返 回 值 , 这 两 个 方 法 在 编 译 期 生 成 。 调 用 BeginInvoke 后 委 托 立<br />

即 返 回 ; 调 用 EndInvoke 时 倘 若 委 托 方 法 未 执 行 完 毕 , 则 阻 塞 当 前 线 程 至 调 用 完 毕 。<br />

假 设 有 一 个 委 托<br />

public delegate int ASyncHandler(int a,string b,ref string c);<br />

那 么 , 其 BeginInvoke 与 EndInvoke 的 形 式 如 下 :<br />

public IAsyncResult BeginInvoke(int a,string b,ref string c,AsyncCallback callback,object<br />

asyncState);<br />

public int EndInvoke(ref string c,IAsyncResult asyncResult);<br />

也 就 是 说 ,BeginInvoke 与 EndInvoke 的 参 数 列 表 与 当 前 委 托 签 名 有 关 , 可 以 总 结 为 :<br />

public IAsyncResult BeginInvoke( 委 托 所 具 有 的 全 部 参 数 ,AsyncCallback callback,object asyncState);<br />

public 委 托 返 回 值 EndInvoke( 委 托 参 数 中 ref/out 部 分 ,IAsyncResult asyncResult);<br />

BeginInvoke 返 回 一 个 IAsyncResult, 其 实 质 是 实 现 IAsyncResult 的<br />

System.Runtime.Remoting.Messaging.AsyncResult 类 。 该 对 象 相 当 于 一 个 “ 凭 证 ”, 在 调 用 EndInvoke<br />

时 用 于 确 认 应 等 待 返 回 的 方 法 ( 猜 测 如 此 )。 就 像 去 银 行 , 存 钱 时 拿 到 一 份 存 折 ( 凭 证 ), 取 款 时 依 据 存 折 ( 凭 证 )<br />

取 款 。<br />

EndInvoke 检 索 委 托 返 回 值 , 并 返 回 标 有 ref/out 的 参 数 值 。<br />

IAsyncResult 接 口 声 明 :<br />

public interface IAsyncResult<br />

{<br />

object AsyncState{get;}<br />

WaitHandle AsyncWaitHandle{get;}<br />

bool CompletedSynchronously{get;}<br />

bool IsCompleted{get;}<br />

}<br />

等 待 调 用 结 束 的 三 种 方 法 :<br />

1、 使 用 EndInvoke 主 动 等 待 异 步 调 用 结 束 。 这 是 最 简 单 的 一 种 方 法 , 适 用 于 非 用 户 界 面 程 序 及 一 些 IO 操 作 ,


因 为 在 调 用 EndInvoke 之 后 当 前 线 程 被 阻 塞 , 除 了 等 待 什 么 都 不 能 做 。<br />

2、 使 用 WaitHandle 等 待 异 步 调 用 结 束 。IAsyncResult 中 有 WaitHandle 成 员 , 获 取 用 于 等 待 异 步 操 作 完 成<br />

的 WaitHandle, 即 调 用 结 束 信 号 。 使 用 WaitHandle.WaitOne() 可 以 阻 塞 当 前 线 程 至 异 步 调 用 完 成 。 这 样 做<br />

的 好 处 是 : 在 调 用 WaitOne 之 后 、EndInvoke 之 前 , 可 以 执 行 其 他 处 理 。<br />

3、 主 动 轮 询 。 使 用 IAsyncResult 中 有 IsCompleted 成 员 检 索 当 前 异 步 调 用 情 况 。 该 方 法 适 用 于 用 户 界 面 程<br />

序 , 想 象 可 在 一 个 循 环 内 做 到 既 等 待 委 托 完 成 , 又 可 以 更 新 用 户 界 面 。<br />

4、 使 用 回 调 , 在 异 步 调 用 结 束 时 执 行 一 个 操 作 。 前 面 的 BeginInvoke 方 法 签 名 的 最 后 两 个 参 数 用 于 回 调 。 需<br />

要 用 到 AsyncCallback 委 托 :<br />

public delegate void AsyncCallback(IAsyncResult asyncResult);<br />

回 调 方 法 在 系 统 线 程 池 中 执 行 。BeginInvoke 的 最 后 一 个 参 数 (object asyncState) 可 以 传 递 包 含 回 调 方 法<br />

将 要 使 用 的 信 息 的 对 象 。 在 回 调 方 法 中 调 用 EndInvoke 可 以 通 过 取 得<br />

System.Runtime.Remoting.Messaging.AsyncResult.AsyncDelegate 实 现 。<br />

个 人 认 为 方 法 1、2 相 差 不 算 太 大 。<br />

先 写 这 么 些 , 以 后 再 补 上 其 他 的 一 些 东 西 。


Mcad 学 习 笔 记 之 异 步 编 程 (AsyncCallback 委 托 ,IAsyncResult 接 口 ,BeginInvoke 方 法 ,EndInvoke 方 法 的<br />

使 用 小 总 结 ) - aierong 原 创 技 术 随 笔 (.Net 方 向 应 用 ) - 博 客 园<br />

Mcad 学 习 笔 记 之 异 步 编 程 (AsyncCallback 委 托 ,IAsyncResult 接 口 ,BeginInvoke 方 法 ,EndInvoke<br />

方 法 的 使 用 小 总 结 )<br />

Posted on 2005-05-25 17:47 aierong 阅 读 (4162) 评 论 (3) 编 辑 收 藏 引 用 网 摘 所 属 分 类 :<br />

MCAD 学 习<br />

让 我 们 来 看 看 同 步 异 步 的 区 别 :<br />

同 步 方 法 调 用 在 程 序 继 续 执 行 之 前 需 要 等 待 同 步 方 法 执 行 完 毕 返 回 结 果<br />

异 步 方 法 则 在 被 调 用 之 后 立 即 返 回 以 便 程 序 在 被 调 用 方 法 完 成 其 任 务 的 同 时 执 行 其 它 操 作<br />

.NET 框 架 基 类 库 中 有 好 几 种 类 都 可 以 提 供 同 步 和 异 步 的 方 法 调 用 。<br />

因 为 同 步 方 法 调 用 会 导 致 程 序 流 程 中 途 等 待 , 所 以 采 用 同 步 方 法 的 情 况 下 往 往 会 导 致 程 序 执 行 的 延 迟<br />

相 比 来 说 , 在 某 些 条 件 下 选 择 异 步 方 法 调 用 就 可 能 更 好 一 些<br />

例 如 , 有 的 时 候 程 序 需 要 给 多 个 Web 服 务 发 出 请 求 , 还 有 远 程 处 理 信 道 (HTTP、TCP) 和 代 理 , 这 时 就<br />

最 好 采 用 异 步 方 法<br />

.NET Framework 允 许 异 步 调 用 任 何 方 法 , 定 义 与 需 要 调 用 的 方 法 具 有 相 同 签 名 的 委 托<br />

CLR 将 自 动 为 该 委 托 定 义 添 加 适 当 签 名 的 BeginInvoke 虚 方 法 和 EndInvoke 虚 方 法 和 Invoke 方 法 。<br />

关 于 委 托 的 这 3 个 方 法 的 详 细 说 明 可 以 参 考 这 文 章<br />

http://www.cnblogs.com/aierong/archive/2005/05/25/162181.html<br />

我 们 先 来 了 解 这 2 个 方 法 和 一 个 委 托 和 一 个 接 口 :<br />

(1)<br />

BeginInvoke 方 法 用 于 启 动 异 步 调 用<br />

它 与 您 需 要 异 步 执 行 的 方 法 具 有 相 同 的 参 数 , 只 不 过 还 有 两 个 额 外 的 参 数 , 将 AsyncCallback 和<br />

AsyncState( 可 通 过<br />

IAsyncResult 接 口 的<br />

AsyncState 属 性 获 得 ) 作 为 最 后 两 个 参 数 , 如 没 有 可 以 为 空 .<br />

BeginInvoke 立 即 返 回 , 不 等 待 异 步 调 用 完 成 。<br />

BeginInvoke 返 回 IasyncResult, 可 用 于 监 视 调 用 进 度 。<br />

结 果 对 象 IAsyncResult 是 从 开 始 操 作 返 回 的 , 并 且 可 用 于 获 取 有 关 异 步 开 始 操 作 是 否 已 完 成 的 状 态 。<br />

结 果 对 象 被 传 递 到 结 束 操 作 , 该 操 作 返 回 调 用 的 最 终 返 回 值 。<br />

在 开 始 操 作 中 可 以 提 供 可 选 的 回 调 。 如 果 提 供 回 调 , 在 调 用 结 束 后 , 将 调 用 该 回 调 ; 并 且 回 调 中 的 代 码 可<br />

以 调 用 结 束 操 作 。<br />

(2)<br />

EndInvoke 方 法 用 于 检 索 异 步 调 用 结 果 。<br />

在 调 用 BeginInvoke 后 可 随 时 调 用 EndInvoke 方 法 , 注 意 : 始 终 在 异 步 调 用 完 成 后 调 用 EndInvoke.<br />

如 果 异 步 调 用 未 完 成 ,EndInvoke 将 一 直 阻 塞 到 异 步 调 用 完 成 。<br />

EndInvoke 的 参 数 包 括 需 要 异 步 执 行 的 方 法 的 out 和 ref 参 数 以 及 由 BeginInvoke 返 回 的<br />

IAsyncResult。<br />

要 注 意 的 是 , 始 终 在 异 步 调 用 完 成 后 调 用 EndInvoke


(3)<br />

AsyncCallback 委 托 用 于 指 定 在 开 始 操 作 完 成 后 应 被 调 用 的 方 法<br />

AsyncCallback 委 托 被 作 为 开 始 操 作 上 的 第 二 个 到 最 后 一 个 参 数 传 递<br />

代 码 原 型 如 下 :<br />

[Serializable]<br />

public delegate void AsyncCallback(IAsyncResult ar);<br />

(4)<br />

IAsyncResult 接 口<br />

它 表 示 异 步 操 作 的 状 态 .<br />

该 接 口 定 义 了 4 个 公 用 属 性<br />

实 际 上 , 发 起 和 完 成 .NET 异 步 调 用 有 4 种 方 案 可 供 你 选 择<br />

1. 方 案 1- 自 己 调 用 EndInvoke 方 法<br />

异 步 执 行 方 法 的 最 简 单 方 式 是 以 BeginInvoke 开 始 , 对 主 线 程 执 行 一 些 操 作 , 然 后 调 用<br />

EndInvoke,EndInvoke 直 到 异 步 调 用 完 成 后 才 返 回<br />

还 是 先 来 段 自 己 喜 欢 的 控 制 台 代 码 :<br />

1using System;<br />

2<br />

3namespace ConsoleApplication1<br />

4{<br />

5 class Class1<br />

6 {<br />

7 public delegate void AsyncEventHandler();<br />

8<br />

9 void Event1()<br />

10 {<br />

11 Console.WriteLine("Event1 Start");<br />

12 System.Threading.Thread.Sleep(2000);<br />

13 Console.WriteLine("Event1 End");<br />

14 }<br />

15<br />

16 void Event2()<br />

17 {<br />

18 Console.WriteLine("Event2 Start");<br />

19 int i=1;<br />

20 while(i


26 }<br />

27<br />

28 void CallbackMethod(IAsyncResult ar)<br />

29 {<br />

30 ((AsyncEventHandler) ar.AsyncState).EndInvoke(ar);<br />

31 }<br />

34 [STAThread]<br />

35 static void Main(string[] args)<br />

36 {<br />

37 long start=0;<br />

38 long end=0;<br />

39 Class1 c = new Class1();<br />

40 Console.WriteLine("ready");<br />

41 start=DateTime.Now.Ticks;<br />

42<br />

43 AsyncEventHandler asy = new AsyncEventHandler(c.Event1);<br />

44 IAsyncResult ia=asy.BeginInvoke(null,null);<br />

45 c.Event2();<br />

46 asy.EndInvoke(ia);<br />

47<br />

48 end =DateTime.Now.Ticks;<br />

49 Console.WriteLine(" 时 间 刻 度 差 ="+ Convert.ToString(end-start) );<br />

50 Console.ReadLine();<br />

51 }<br />

52 }<br />

53}<br />

54<br />

此 程 序 简 单 , 异 步 的 处 理 过 程 在 代 码 43-46 这 几 行<br />

结 果 如 下 :<br />

现 在 让 我 们 来 看 看 同 步 处 理<br />

修 改 代 码 43-46 这 几 行 代 码 :<br />

c.Event1();<br />

c.Event2();<br />

结 果 如 下 :<br />

前 者 的 时 间 刻 度 大 大 小 于 后 者<br />

我 们 可 以 明 显 地 看 到 异 步 运 行 的 速 度 优 越 性<br />

2. 方 案 2- 采 用 查 询 (IsCompleted 属 性 )<br />

IAsyncResult.IsCompleted 属 性 获 取 异 步 操 作 是 否 已 完 成 的 指 示 , 发 现 异 步 调 用 何 时 完 成 .<br />

再 次 修 改 代 码 43-46 这 几 行 代 码 :<br />

AsyncEventHandler asy = new AsyncEventHandler(c.Event1);


IAsyncResult ia=asy.BeginInvoke(null,null);<br />

c.Event2();<br />

while(!ia.IsCompleted)<br />

{<br />

}<br />

asy.EndInvoke(ia);<br />

3. 方 案 3- 采 用 AsyncWaitHandle 来 等 待 方 法 调 用 的 完 成<br />

IAsyncResult.AsyncWaitHandle 属 性 获 取 用 于 等 待 异 步 操 作 完 成 的 WaitHandle<br />

WaitHandle.WaitOne 方 法 阻 塞 当 前 线 程 , 直 到 当 前 的 WaitHandle 收 到 信 号<br />

使 用 WaitHandle, 则 在 异 步 调 用 完 成 之 后 , 但 在 通 过 调 用 EndInvoke 结 果 之 前 , 可 以 执 行 其 他 处 理<br />

再 次 修 改 代 码 43-46 这 几 行 代 码 :<br />

AsyncEventHandler asy = new AsyncEventHandler(c.Event1);<br />

IAsyncResult ia=asy.BeginInvoke(null,null);<br />

c.Event2();<br />

ia.AsyncWaitHandle.WaitOne();<br />

4. 方 案 4- 利 用 回 调 函 数<br />

如 果 启 动 异 步 调 用 的 线 程 不 需 要 处 理 调 用 结 果 , 则 可 以 在 调 用 完 成 时 执 行 回 调 方 法<br />

要 使 用 回 调 方 法 , 必 须 将 代 表 该 方 法 的 AsyncCallback 委 托 传 递 给 BeginInvoke<br />

再 次 修 改 代 码 43-46 这 几 行 代 码 :<br />

AsyncEventHandler asy = new AsyncEventHandler(c.Event1);<br />

asy.BeginInvoke(new AsyncCallback(c.CallbackMethod),asy);<br />

c.Event2();<br />

希 望 上 面 提 到 的 知 识 对 你 有 所 提 示<br />

当 然 欢 迎 交 流 和 指 正<br />

blog:<br />

http://www.cnblogs.com/aierong<br />

author:aierong<br />

email:aierong@126.com<br />

Feedback<br />

# re:<br />

Mcad 学 习 笔 记 之 异 步 编 程 (AsyncCallback 委 托 ,IAsyncResult 接 口 ,BeginInvoke 方 法 ,EndInvoke<br />

方 法 的 使 用 小 总 结 )<br />

回 复 更 多 评 论<br />

2005-05-26 09:14 by eric<br />

问 个 题 外 话 , 考 Mcsd for .net 怎 么 报 名 ? 准 备 呢 ? 微 软 中 文 认 证 主 页 上 , 找 不 到 相 关 的 信 息 啊 ; 能 给 些<br />

相 关 的 资 源 吗 ? 谢 谢 。<br />

# re:<br />

Mcad 学 习 笔 记 之 异 步 编 程 (AsyncCallback 委 托 ,IAsyncResult 接 口 ,BeginInvoke 方 法 ,EndInvoke<br />

方 法 的 使 用 小 总 结 )<br />

回 复 更 多 评 论


2005-05-26 09:59 by aierong<br />

to:eric<br />

我 也 没 考 过<br />

具 体 的 我 也 不 知 道<br />

http://www.examlink.com/<br />

你 去 看 看<br />

# 使 用 委 托 进 行 异 步 编 程 [TrackBack] 回 复 更 多 评 论<br />

2007-04-19 10:50 by 落 叶<br />

1. 什 么 是 异 步 编 程 使 用 .NET 异 步 编 程 , 在 程 序 继 续 执 行 的 同 时 对 .NET 类 方 法 进 行 调 用 , 直 到 进 行 指 定 的<br />

回 调 为 止 ; 如 果 没 有 提 供 回 调 , 则 直 到 对 调 用 的 阻 塞 、 轮 询 或 等 待 完 成 为 止 。 例 如 , 一 个 程 序 可 以 调 用 一 ...<br />

查 看 原 文


UI 设 计 注 意 点<br />

软 件 的 智 能 和 记 忆 功 能<br />

1. 用 户 登 录 界 面 最 好 有 用 户 名 和 ID 的 记 忆 , 焦 点 直 接 定 位 到 密 码 输 入 框<br />

2. 单 据 录 入 界 面 最 好 有 保 存 和 载 入 默 认 值 的 功 能<br />

3. 单 据 搜 索 界 面 可 以 保 存 用 户 自 定 义 的 各 种 搜 索 条 件 组 合<br />

4. 用 户 调 整 过 的 GRID 的 列 宽 , 窗 口 的 位 置 可 以 自 动 记 忆<br />

5. 系 统 可 以 根 据 用 户 的 使 用 频 度 对 相 关 功 能 进 行 自 动 的 优 先 级 排 序<br />

6. 系 统 能 够 记 忆 不 同 用 户 的 使 用 偏 好 , 使 用 系 统 的 固 有 模 式 和 常 用 的 自 定 义 设 置<br />

减 少 不 必 要 的 重 复 交 互<br />

1. 减 少 不 必 要 的 各 种 操 作 , 能 够 点 一 次 鼠 标 或 敲 一 次 键 盘 完 成 的 绝 不 作 出 两 次 或 多 次 。<br />

2. 提 示 信 息 要 适 度 , 太 多 不 好 , 太 少 也 不 好 。<br />

3. 数 据 项 完 整 性 校 验 问 题 要 注 意 光 标 焦 点 自 动 定 位 到 错 误 处<br />

4. 完 整 业 务 功 能 不 要 让 用 户 在 多 个 窗 口 切 换 多 次 才 能 够 完 成 。 尽 量 减 少 这 种 切 换 。<br />

5. 为 了 方 便 用 户 切 换 窗 口 , 相 关 的 表 单 最 好 都 作 为 非 模 式 的 形 式 。<br />

6. 相 同 的 信 息 不 要 让 用 户 在 系 统 中 多 处 或 多 次 录 入 , 保 证 入 口 的 唯 一 性<br />

7. 系 统 要 尽 可 能 根 据 用 户 已 经 录 入 信 息 自 动 获 取 其 它 附 属 信 息 , 而 不 需 要 用 户 重 复 的 选 择 或 录<br />

入 。<br />

导 航 和 界 面 跳 转<br />

1. 表 单 新 弹 出 对 话 框 , 对 话 框 再 弹 出 对 话 框 的 这 种 层 次 要 控 制 在 3 层 以 内 。


2. 所 有 的 非 模 式 活 动 窗 口 最 好 有 类 似 桌 面 任 务 栏 一 样 的 停 靠 方 式 , 方 便 切 换 窗 口<br />

3. 系 统 可 以 支 持 用 户 自 己 定 义 常 用 功 能 和 菜 单<br />

4. 对 于 常 用 功 能 应 该 提 供 便 捷 的 快 捷 键 和 工 具 栏 按 钮<br />

5. 对 于 系 统 中 提 供 的 各 种 业 务 和 表 单 功 能 能 够 让 用 户 便 捷 挑 转 到 帮 助 信 息 上<br />

6. 对 表 单 和 界 面 联 动 和 交 互 的 时 候 要 注 意 相 关 界 面 数 据 的 自 动 刷 新<br />

7. 一 个 窗 口 中 最 多 不 要 出 现 超 过 三 个 的 GRID 控 件<br />

8.BS 方 式 不 要 左 右 滚 屏 。CS 模 式 既 要 避 免 左 右 滚 屏 也 要 避 免 上 下 滚 屏<br />

9. 需 要 根 据 业 务 查 看 需 求 和 数 据 的 展 现 需 求 来 选 择 合 适 的 界 面 控 件<br />

系 统 性 能 和 健 壮 性 方 面 的<br />

1. 系 统 中 相 关 的 耗 时 操 作 都 必 须 必 须 转 变 鼠 标 为 等 待 状 态<br />

2. 系 统 耗 时 操 作 超 过 30 秒 的 最 好 能 够 提 供 给 用 户 相 关 的 进 度 条 功 能<br />

3. 系 统 耗 时 功 能 超 过 2 分 钟 的 最 好 能 够 设 计 为 异 步 多 线 程 的 方 式 进 行 处 理<br />

4. 系 统 应 用 有 友 好 的 完 整 性 和 约 束 校 验 的 提 示 信 息 , 方 便 用 户 修 改 录 入 数 据<br />

5. 在 系 统 出 现 异 常 情 况 下 应 该 有 友 好 的 统 一 的 提 示 信 息 , 同 时 后 台 应 该 记 录 详 细 的 异 常 日 志<br />

界 面 友 好 性 和 易 用 性 方 面 的<br />

1. 表 单 应 该 能 够 根 据 屏 幕 分 辩 率 自 动 适 应 。 在 界 面 上 让 用 户 一 次 能 够 看 到 足 够 多 的 信 息<br />

2. 表 单 应 该 支 持 Tab 键 功 能 , 顺 序 为 从 左 到 右 , 从 上 到 下 。<br />

3. 常 用 的 表 单 应 该 同 时 支 持 键 盘 操 作 和 鼠 标 操 作 。<br />

4. 界 面 上 控 件 的 布 局 应 该 间 距 适 当 , 标 签 和 控 件 对 齐 , 有 适 当 的 录 入 提 示 信 息 。<br />

5. 界 面 的 配 色 应 该 尽 量 简 单 , 尽 量 少 使 用 各 种 刺 眼 的 颜 色


6. 用 户 看 到 表 单 后 应 该 就 基 本 清 楚 相 关 功 能 , 表 单 要 尽 量 自 我 解 释 , 不 要 设 计 过 多 的 隐 含 在 界 面<br />

里 面 功 能<br />

数 据 的 录 入 和 检 索<br />

1. 根 据 业 务 需 要 选 择 适 合 的 数 据 录 入 控 件<br />

2. 数 据 录 入 控 件 应 该 有 完 备 的 数 据 完 整 性 和 一 致 性 校 验 功 能<br />

3. 系 统 应 该 提 供 用 户 暂 时 保 存 录 入 数 据 的 功 能<br />

4. 能 够 自 动 获 取 数 据 不 要 让 用 户 再 去 录 入 , 能 够 选 择 录 入 数 据 不 要 让 用 户 手 工 录 入<br />

5. 数 据 检 索 条 件 应 该 适 中 , 不 应 太 多 也 不 应 太 少 。 检 索 支 持 组 合 条 件 检 索 。<br />

6. 为 了 满 足 不 同 需 求 检 索 可 以 提 供 简 单 检 索 和 高 级 检 索 多 种 方 式 。<br />

7. 应 该 在 第 一 时 间 提 供 给 用 户 检 索 数 据 , 因 此 检 索 功 能 存 在 性 能 问 题 时 候 要 考 虑 分 页 。<br />

8. 在 检 索 功 能 较 耗 时 的 时 候 应 该 提 供 给 用 户 相 关 的 进 度 条 显 示 进 度<br />

9. 表 格 最 好 能 够 提 供 行 显 示 和 列 显 示 等 多 种 显 示 模 式 , 方 面 用 户 查 看 数 据


C# 是 一 门 支 持 多 线 程 的 语 言 , 因 此 线 程 的 使 用 也 是 比 较 常 见 的 。 由 于 线 程 的 知 识 在 Win32 编 程 的 时 候 已 经 说 得<br />

过 多 , 所 以 在 .Net 中 很 少 介 绍 这 部 分 ( 可 能 .Net 不 觉 得 这 部 分 是 它 所 特 有 的 )。<br />

那 么 线 程 相 关 的 问 题 大 致 有 如 下 四 类 ( 这 篇 文 章 只 讨 论 单 线 程 、 单 线 程 与 UI 线 程 这 两 方 面 的 问 题 )。<br />

问 题 一 , 线 程 的 基 本 操 作 , 例 如 : 暂 停 、 继 续 、 停 止 等 ;<br />

问 题 二 , 如 何 向 线 程 传 递 参 数 或 者 从 中 得 到 其 返 回 值 ;<br />

问 题 三 , 如 何 使 线 程 所 占 用 的 CPU 不 要 老 是 百 分 之 百 ;<br />

最 后 一 个 , 也 是 问 题 最 多 的 , 就 是 如 何 在 子 线 程 来 控 制 UI 中 的 控 件 , 换 句 话 说 , 就 是 在 线 程 中 控 制 窗 体 某 些 控<br />

件 的 显 示 。<br />

对 于 问 题 一 , 我 不 建 议 使 用 Thread 类 提 供 的 Suspend、Resume 以 及 Abort 这 三 个 方 法 , 前 两 个 有 问 题 , 好<br />

像 在 VS05 已 经 屏 蔽 这 两 个 方 法 ; 对 于 Abort 来 说 , 除 了 资 源 没 有 得 到 及 时 释 放 外 , 有 时 候 会 出 现 异 常 。 如 何<br />

做 呢 , 通 过 设 置 开 关 变 量 来 完 成 。<br />

对 于 问 题 二 , 我 不 建 议 使 用 静 态 成 员 来 完 成 , 仅 仅 为 了 线 程 而 破 坏 类 的 封 装 有 些 得 不 偿 失 。 那 如 何 做 呢 , 通 过 创<br />

建 单 独 的 线 程 类 来 完 成 。<br />

对 于 问 题 三 来 说 , 造 成 这 个 原 因 是 由 于 线 程 中 进 行 不 间 断 的 循 环 操 作 , 从 而 使 CPU 完 全 被 子 线 程 占 有 。 那 么 处<br />

理 此 类 问 题 , 其 实 很 简 单 , 在 适 当 的 位 置 调 用 Thread.Sleep(20) 来 释 放 所 占 有 CPU 资 源 , 不 要 小 看 这 20 毫<br />

秒 的 睡 眠 , 它 的 作 用 可 是 巨 大 的 , 可 以 使 其 他 线 程 得 到 CPU 资 源 , 从 而 使 你 的 CPU 使 用 效 率 降 下 来 。<br />

看 完 前 面 的 三 个 问 题 的 解 释 , 对 于 如 何 做 似 乎 没 有 给 出 一 个 明 确 的 答 案 , 为 了 更 好 地 说 明 如 何 解 决 这 三 个 问 题 ,<br />

我 用 一 个 比 较 完 整 的 例 子 展 现 给 大 家 , 代 码 如 下 。<br />

//--------------------------- Sub-thread class ---------------------------------------<br />

//------------------------------------------------------------------------------------<br />

//---File:<br />

clsSubThread


---Description:<br />

The sub-thread template class file<br />

//---Author:<br />

Knight<br />

//---Date: Aug.21, 2006<br />

//------------------------------------------------------------------------------------<br />

//---------------------------{Sub-thread class}---------------------------------------<br />

namespace ThreadTemplate<br />

{<br />

using System;<br />

using System.Threading;<br />

using System.IO;<br />

/// <br />

/// Summary description for clsSubThread.<br />

/// <br />

public class clsSubThread:IDisposable<br />

{<br />

private Thread thdSubThread = null;<br />

private Mutex mUnique = new Mutex();<br />

private bool blnIsStopped;<br />

private bool blnSuspended;<br />

private bool blnStarted;<br />

private int nStartNum;


public bool IsStopped<br />

{<br />

get{ return blnIsStopped; }<br />

}<br />

public bool IsSuspended<br />

{<br />

get{ return blnSuspended; }<br />

}<br />

public int ReturnValue<br />

{<br />

get{ return nStartNum;}<br />

}<br />

public clsSubThread( int StartNum )<br />

{<br />

//<br />

// TODO: Add constructor logic here<br />

//<br />

blnIsStopped = true;<br />

blnSuspended = false;


lnStarted = false;<br />

nStartNum = StartNum;<br />

}<br />

/// <br />

/// Start sub-thread<br />

/// <br />

public void Start()<br />

{<br />

if( !blnStarted )<br />

{<br />

thdSubThread = new Thread( new ThreadStart( SubThread ) );<br />

blnIsStopped = false;<br />

blnStarted = true;<br />

thdSubThread.Start();<br />

}<br />

}<br />

/// <br />

/// Thread entry function<br />

///


private void SubThread()<br />

{<br />

do<br />

{<br />

// Wait for resume-command if got suspend-command here<br />

mUnique.WaitOne();<br />

mUnique.ReleaseMutex();<br />

nStartNum++;<br />

Thread.Sleep(1000); // Release CPU here<br />

}while( blnIsStopped == false );<br />

}<br />

/// <br />

/// Suspend sub-thread<br />

/// <br />

public void Suspend()<br />

{<br />

if( blnStarted && !blnSuspended )<br />

{<br />

blnSuspended = true;


mUnique.WaitOne();<br />

}<br />

}<br />

/// <br />

/// Resume sub-thread<br />

/// <br />

public void Resume()<br />

{<br />

if( blnStarted && blnSuspended )<br />

{<br />

blnSuspended = false;<br />

mUnique.ReleaseMutex();<br />

}<br />

}<br />

/// <br />

/// Stop sub-thread<br />

/// <br />

public void Stop()<br />

{<br />

if( blnStarted )


{<br />

if( blnSuspended )<br />

Resume();<br />

blnStarted = false;<br />

blnIsStopped = true;<br />

thdSubThread.Join();<br />

}<br />

}<br />

#region IDisposable Members<br />

/// <br />

/// Class resources dispose here<br />

/// <br />

public void Dispose()<br />

{<br />

// TODO: Add clsSubThread.Dispose implementation<br />

Stop();//Stop thread first<br />

GC.SuppressFinalize( this );<br />

}<br />

#endregion<br />

}


}<br />

那 么 对 于 调 用 呢 , 就 非 常 简 单 了 , 如 下 :<br />

// Create new sub-thread object with parameters<br />

clsSubThread mySubThread = new clsSubThread( 5 );<br />

mySubThread.Start();//Start thread<br />

Thread.Sleep( 2000 );<br />

mySubThread.Suspend();//Suspend thread<br />

Thread.Sleep( 2000 );<br />

mySubThread.Resume();//Resume thread<br />

Thread.Sleep( 2000 );<br />

mySubThread.Stop();//Stop thread<br />

//Get thread's return value<br />

Debug.WriteLine( mySubThread.ReturnValue );<br />

//Release sub-thread object<br />

mySubThread.Dispose();


在 回 过 头 来 看 看 前 面 所 说 的 三 个 问 题 。<br />

对 于 问 题 一 来 说 , 首 先 需 要 局 部 成 员 的 支 持 , 那 么<br />

private Mutex mUnique = new Mutex();<br />

private bool blnIsStopped;<br />

private bool blnSuspended;<br />

private bool blnStarted;<br />

光 看 成 员 名 称 , 估 计 大 家 都 已 经 猜 出 其 代 表 的 意 思 。 接 下 来 需 要 修 改 线 程 入 口 函 数 , 要 是 这 些 开 关 变 量 能 发 挥 作<br />

用 , 那 么 看 看 SubThread 这 个 函 数 。<br />

/// <br />

/// Thread entry function<br />

/// <br />

private void SubThread()<br />

{<br />

do<br />

{<br />

// Wait for resume-command if got suspend-command here<br />

mUnique.WaitOne();<br />

mUnique.ReleaseMutex();<br />

nStartNum++;


Thread.Sleep(1000);<br />

}while( blnIsStopped == false );<br />

}<br />

函 数 比 较 简 单 , 不 到 十 句 , 可 能 对 于 “blnIsStopped == false” 这 个 判 断 来 说 , 大 家 还 比 较 好 理 解 , 这 是 一 个<br />

普 通 的 判 断 , 如 果 当 前 Stop 开 关 打 开 了 , 就 停 止 循 环 ; 否 则 一 直 循 环 。<br />

大 家 比 较 迷 惑 的 可 能 是 如 下 这 两 句 :<br />

mUnique.WaitOne();<br />

mUnique.ReleaseMutex();<br />

这 两 句 的 目 的 是 为 了 使 线 程 在 Suspend 操 作 的 时 候 能 发 挥 效 果 , 为 了 解 释 这 两 句 , 需 要 结 合 Suspend 和<br />

Resume 这 两 个 方 法 , 它 俩 的 代 码 如 下 。<br />

/// <br />

/// Suspend sub-thread<br />

/// <br />

public void Suspend()<br />

{<br />

if( blnStarted && !blnSuspended )<br />

{<br />

blnSuspended = true;<br />

mUnique.WaitOne();<br />

}<br />

}


/// Resume sub-thread<br />

/// <br />

public void Resume()<br />

{<br />

if( blnStarted && blnSuspended )<br />

{<br />

blnSuspended = false;<br />

}<br />

mUnique.ReleaseMutex();<br />

}<br />

为 了 更 好 地 说 明 , 还 需 要 先 简 单 说 说 Mutex 类 型 。 对 于 此 类 型 对 象 , 当 调 用 对 象 的 WaitOne 之 后 , 如 果 此 时 没<br />

有 其 他 线 程 对 它 使 用 的 时 候 , 就 立 刻 获 得 信 号 量 , 继 续 执 行 代 码 ; 当 再 调 用 ReleaseMutex 之 前 , 如 果 再 调 用<br />

对 象 的 WaitOne 方 法 , 就 会 一 直 等 待 , 直 到 获 得 信 号 量 的 调 用 ReleaseMutex 来 进 行 释 放 。 这 就 好 比 卫 生 间 的<br />

使 用 , 如 果 没 有 人 使 用 则 可 以 直 接 使 用 , 否 则 只 有 等 待 。<br />

明 白 了 这 一 点 后 , 再 来 解 释 这 两 句 所 能 出 现 的 现 象 。<br />

mUnique.WaitOne();<br />

mUnique.ReleaseMutex();<br />

当 在 线 程 函 数 中 , 执 行 到 “mUnique.WaitOne();” 这 一 句 的 时 候 , 如 果 此 时 外 界 没 有 发 送 Suspend 消 息 , 也<br />

就 是 信 号 量 没 有 被 占 用 , 那 么 这 一 句 可 以 立 刻 返 回 。 那 么 为 什 么 要 紧 接 着 释 放 呢 , 因 为 不 能 总 占 着 信 号 量 , 立 即<br />

释 放 信 号 量 是 避 免 在 发 送 Suspend 命 令 的 时 候 出 现 等 待 ; 如 果 此 时 外 界 已 经 发 送 了 Suspend 消 息 , 也 就 是 说<br />

信 号 量 已 经 被 占 用 , 此 时 “mUnique.WaitOne();” 不 能 立 刻 返 回 , 需 要 等 到 信 号 量 被 释 放 才 能 继 续 进 行 , 也<br />

就 是 需 要 调 用 Resume 的 时 候 ,“mUnique.WaitOne();” 才 能 获 得 信 号 量 进 行 继 续 执 行 。 这 样 才 能 达 到 真 正<br />

意 义 上 的 Suspend 和 Resume。<br />

至 于 线 程 的 Start 和 Stop 来 说 , 相 对 比 较 简 单 , 这 里 我 就 不 多 说 了 。<br />

现 在 再 来 分 析 一 下 问 题 二 , 其 实 例 子 比 较 明 显 , 是 通 过 构 造 函 数 和 属 性 来 完 成 参 数 和 返 回 值 , 这 一 点 我 也 不 多 说<br />

了 。 如 果 线 程 参 数 比 较 多 的 话 , 可 以 考 虑 属 性 来 完 成 , 类 似 于 返 回 值 。<br />

问 题 三 , 我 就 更 不 用 多 说 了 。 有 人 说 了 , 如 果 子 线 程 中 的 循 环 不 能 睡 眠 怎 么 办 , 因 为 睡 眠 的 话 , 有 时 会 造 成 数 据<br />

丢 失 , 这 方 面 的 可 以 借 鉴 前 面 Suspend 的 做 法 , 如 果 更 复 杂 , 则 牵 扯 到 多 线 程 的 同 步 问 题 , 这 部 分 我 会 稍 后 单<br />

独 写 一 篇 文 章 。


前 三 个 问 题 解 决 了 , 该 说 说 最 常 见 的 问 题 , 如 何 在 子 线 程 中 控 制 窗 体 控 件 。 这 也 是 写 线 程 方 面 程 序 经 常 遇 到 的 ,<br />

其 实 我 以 前 写 过 两 篇 文 章 , 都 对 这 方 面 做 了 部 分 介 绍 。 那 么 大 家 如 果 有 时 间 的 话 , 不 妨 去 看 看 。<br />

http://blog.csdn.net/knight94/archive/2006/03/16/626584.aspx<br />

http://blog.csdn.net/knight94/archive/2006/05/27/757351.aspx<br />

首 先 说 说 , 为 什 么 不 能 直 接 在 子 线 程 中 操 纵 UI 呢 。 原 因 在 于 子 线 程 和 UI 线 程 属 于 不 同 的 上 下 文 , 换 句 比 较 通 俗<br />

的 话 说 , 就 好 比 两 个 人 在 不 同 的 房 间 里 一 样 , 那 么 要 你 直 接 操 作 另 一 个 房 间 里 的 东 西 , 恐 怕 不 行 罢 , 那 么 对 于 子<br />

线 程 来 说 也 一 样 , 不 能 直 接 操 作 UI 线 程 中 的 对 象 。<br />

那 么 如 何 在 子 线 程 中 操 纵 UI 线 程 中 的 对 象 呢 ,.Net 提 供 了 Invoke 和 BeginInvoke 这 两 种 方 法 。 简 单 地 说 ,<br />

就 是 子 线 程 发 消 息 让 UI 线 程 来 完 成 相 应 的 操 作 。<br />

这 两 个 方 法 有 什 么 区 别 , 这 在 我 以 前 的 文 章 已 经 说 过 了 ,Invoke 需 要 等 到 所 调 函 数 的 返 回 , 而 BeginInvoke<br />

则 不 需 要 。<br />

用 这 两 个 方 法 需 要 注 意 的 , 有 如 下 三 点 :<br />

第 一 个 是 由 于 Invoke 和 BeginInvoke 属 于 Control 类 型 的 成 员 方 法 , 因 此 调 用 的 时 候 , 需 要 得 到 Control 类<br />

型 的 对 象 才 能 触 发 , 也 就 是 说 你 要 触 发 窗 体 做 什 么 操 作 或 者 窗 体 上 某 个 控 件 做 什 么 操 作 , 需 要 把 窗 体 对 象 或 者 控<br />

件 对 象 传 递 到 线 程 中 。<br />

第 二 个 , 对 于 Invoke 和 BeginInvoke 接 受 的 参 数 属 于 一 个 delegate 类 型 , 我 在 以 前 的 文 章 中 使 用 的 是<br />

MethodInvoker, 这 是 .Net 自 带 的 一 个 delegate 类 型 , 而 并 不 意 味 着 在 使 用 Invoke 或 者 BeginInvoke 的 时<br />

候 只 能 用 它 。 参 看 我 给 的 第 二 篇 文 章 (《 如 何 弹 出 一 个 模 式 窗 口 来 显 示 进 度 条 》), 会 有 很 多 不 同 的 delegate 定 义 。<br />

最 后 一 个 , 使 用 Invoke 和 BeginInvoke 有 个 需 要 注 意 的 , 就 是 当 子 线 程 在 Form_Load 开 启 的 时 候 , 会 遇 到<br />

异 常 , 这 是 因 为 触 发 Invoke 的 对 象 还 没 有 完 全 初 始 化 完 毕 。 处 理 此 类 问 题 , 在 开 启 线 程 之 前 显 式 的 调 用<br />

“this.Show();”, 来 使 窗 体 显 示 在 线 程 开 启 之 前 。 如 果 此 时 只 是 开 启 线 程 来 初 始 化 显 示 数 据 , 那 我 建 议 你 不 要<br />

使 用 子 线 程 , 用 Splash 窗 体 的 效 果 可 能 更 好 。 这 方 面 可 以 参 看 如 下 的 例 子 。<br />

http://www.syncfusion.com/FAQ/WindowsForms/FAQ_c95c.aspx#q621q<br />

线 程 的 四 个 相 关 问 题 已 经 说 完 了 , 这 篇 文 章 只 说 了 单 线 程 , 以 及 单 线 程 与 UI 线 程 交 互 的 问 题 。 其 中 涉 及 到 的 方<br />

法 不 一 定 是 唯 一 的 , 因 为 .Net 还 提 供 了 其 他 类 来 扶 助 线 程 操 作 , 这 里 就 不 一 一 罗 列 。 至 于 多 线 程 之 间 的 同 步 , 我<br />

会 稍 后 专 门 写 篇 文 章 进 行 描 述 。


VS2005 中 更 新 其 他 线 程 访 问 界 面 线 程 控 件 的 方 法<br />

作 者 :Depraved_Survival | 录 入 时 间 :2007-9-4 | 点 击 :17 次 打 印 此 文 章 | 字 体 : 大 中 小<br />

VS2005 中 , 界 面 线 程 中 的 控 件 如 果 由 其 他 线 程 进 行 更 新 时 , 编 译 器 会 自 动 抛 出 异 常 来 避 免 这 种 不 安 全 的<br />

跨 线 程 访 问 方 式 。 解 决 这 个 问 题 的 一 种 方 法 是 在 界 面 线 程 中 编 写 控 件 内 容 更 新 程 序 , 并 声 明 委 托 , 利 用 Invoke<br />

方 法 进 行 实 现 。 具 体 实 现 方 法 如 下 。<br />

假 设 我 们 的 From1 中 有 一 个 TextBox 空 间 , 名 为 txtBox,From1 在 Load 的 时 候 启 动 一 个 线 程<br />

thread, 线 程 处 理 函 数 为 threadProc, 在 该 线 程 中 将 Hello the world 写 入 txtBox 控 件 中 。<br />

对 于 上 面 的 问 题 , 在 以 往 VS2003 中 我 们 只 需 在 thread 线 程 的 处 理 函 数 中 加 入<br />

this.txtBox.Text="Hello the world!" 即 可 , 而 在 VS2005 中 同 样 的 写 法 编 译 器 会 抛 出 异 常 。 在 VS2005 中<br />

正 确 的 调 用 方 法 是 先 声 明 用 于 更 新 空 间 的 委 托 , 在 定 义 一 个 更 新 控 件 用 的 方 法 , 然 后 在 线 程 函 数 中 通 过 From1<br />

的 InvokeRequired 方 法 判 断 是 否 需 要 采 用 Invoke 方 式 , 如 果 需 要 使 用 Invoke 方 式 访 问 , 否 则 采 用<br />

VS2003 中 的 方 式 更 新 控 件 内 容 , 代 码 如 下 :<br />

// 声 明 更 新 控 件 用 的 代 理 委 托<br />

protected delegate void UpdateControlText(string strText);<br />

// 定 义 更 新 控 件 的 方 法<br />

protected void updateControlText(string strText)<br />

{<br />

txtBox.Text = strText;<br />

return;<br />

}<br />

需 要 说 明 的 是 , 上 述 委 托 和 方 法 为 From1 对 应 类 对 象 的 成 员 , 委 托 的 参 数 必 须 与 方 法 的 参 数 完 全 相 同 。<br />

在 线 程 中 需 要 更 新 控 件 的 位 置 中 添 加 如 下 代 码 :<br />

if (this.InvokeRequired)<br />

{<br />

UpdateControlText update = new UpdateControlText(updateControlText);<br />

this.Invoke(update, "Hello the world!");<br />

}<br />

else<br />

{<br />

this.txtBox.Text="Hello the world!";<br />

}<br />

Invoke 方 法 中 第 一 个 参 数 为 委 托 类 型 的 对 象 , 构 造 该 对 象 的 时 候 采 用 updateControlText 作 为 构 造 参 数 。 从<br />

第 二 个 参 数 开 始 为 一 个 params object [] objParams 的 队 列 , 可 以 容 纳 任 意 多 任 意 类 型 的 参 数 , 但 是 针 对 每<br />

次 调 用 来 说 必 须 要 指 定 类 型 个 数 与 UpdateControlText 完 全 相 同 的 参 数 表 。 虽 然 参 数 不 同 时 编 译 器 不 会 报 错 ,<br />

但 是 会 在 运 行 中 弹 出 异 常 。


借 助 WebService 实 现 多 线 程 上 传 文 件 - 愚 翁 专 栏 - CSDNBlog 正 在 处 理 您 的 请 求 ...<br />

借 助 WebService 实 现 多 线 程 上 传 文 件<br />

在 WebService 的 帮 助 下 , 进 行 多 线 程 上 传 文 件 是 非 常 简 单 。 因 此 我 只 做 个 简 单 的 例 子 , 那 么 如 果 想 要 实 现 此 功<br />

能 的 朋 友 , 可 以 在 我 的 基 础 上 进 行 扩 展 。<br />

首 先 说 说 服 务 器 端 , 只 需 要 提 供 一 个 能 允 许 多 线 程 写 文 件 的 函 数 即 可 , 具 体 代 码 如 下 。<br />

[WebMethod]<br />

public bool UploadFileData( string FileName, int StartPosition, byte[] bData )<br />

{<br />

string strFullName = Server.MapPath( "Uploads" ) + @"\" + FileName;<br />

FileStream fs = null;<br />

try<br />

{<br />

fs = new FileStream( strFullName, FileMode.OpenOrCreate,<br />

FileAccess.Write, FileShare.Write );<br />

}<br />

catch( IOException err )<br />

{<br />

Session["ErrorMessage"] = err.Message;<br />

return false;<br />

}<br />

}<br />

using( fs )<br />

{<br />

fs.Position = StartPosition;<br />

fs.Write( bData, 0, bData.Length );<br />

}<br />

return true;<br />

其 中 “Uploads” 是 在 服 务 程 序 所 在 目 录 下 的 一 个 子 目 录 , 需 要 设 置 ASPNET 用 户 对 此 目 录 具 有 可 写 权 限 。<br />

相 对 于 服 务 器 端 来 说 , 客 户 端 要 稍 微 复 杂 一 些 , 因 为 要 牵 扯 到 多 线 程 的 问 题 。 为 了 更 好 的 传 递 参 数 , 我 用 一 个 线<br />

程 类 来 完 成 。 具 体 如 下 。<br />

public delegate void UploadFileData( string FileName, int StartPos, byte[]<br />

bData );<br />

/// <br />

/// FileThread: a class for sub-thread<br />

/// <br />

sealed class FileThread<br />

{<br />

private int nStartPos;


private int nTotalBytes;<br />

private string strFileName;<br />

public static UploadFileData UploadHandle;<br />

/// <br />

/// Constructor<br />

/// <br />

/// <br />

/// <br />

/// <br />

public FileThread( int StartPos, int TotalBytes, string FileName )<br />

{<br />

//Init thread variant<br />

nStartPos = StartPos;<br />

nTotalBytes = TotalBytes;<br />

strFileName = FileName;<br />

byte:{2}",<br />

}<br />

//Only for debug<br />

Debug.WriteLine( string.Format( "File name:{0} position: {1} total<br />

strFileName, nStartPos, nTotalBytes ) );<br />

/// <br />

/// Sub-thread entry function<br />

/// <br />

/// <br />

public void UploadFile( object stateinfo )<br />

{<br />

int nRealRead, nBufferSize;<br />

const int BUFFER_SIZE = 10240;<br />

using( FileStream fs = new FileStream( strFileName,<br />

FileMode.Open, FileAccess.Read,<br />

FileShare.Read ) )<br />

{<br />

string sName = strFileName.Substring( strFileName.LastIndexOf(<br />

"\\" ) + 1 );<br />

byte[] bBuffer = new byte[BUFFER_SIZE];//Init 10k buffer<br />

fs.Position = nStartPos;<br />

nRealRead = 0;<br />

do<br />

{<br />

nBufferSize = BUFFER_SIZE;


if( nRealRead + BUFFER_SIZE > nTotalBytes )<br />

nBufferSize = nTotalBytes - nRealRead;<br />

nBufferSize = fs.Read( bBuffer, 0, nBufferSize );<br />

if( nBufferSize == BUFFER_SIZE )<br />

UploadHandle( sName,<br />

nRealRead + nStartPos,<br />

bBuffer );<br />

else if( nBufferSize > 0 )<br />

{<br />

//Copy data<br />

byte[] bytData = new byte[nBufferSize];<br />

Array.Copy( bBuffer,0, bytData, 0, nBufferSize );<br />

}<br />

UploadHandle( sName,<br />

nRealRead + nStartPos,<br />

bytData );<br />

}<br />

}<br />

nRealRead += nBufferSize;<br />

}<br />

while( nRealRead < nTotalBytes );<br />

}<br />

//Release signal<br />

ManualResetEvent mr = stateinfo as ManualResetEvent;<br />

if( mr != null )<br />

mr.Set();<br />

那 么 在 执 行 的 时 候 , 要 创 建 线 程 类 对 象 , 并 为 每 一 个 每 个 线 程 设 置 一 个 信 号 量 , 从 而 能 在 所 有 线 程 都 结 束 的 时 候<br />

得 到 通 知 , 大 致 的 代 码 如 下 。<br />

FileInfo fi = new FileInfo( txtFileName.Text );<br />

if( fi.Exists )<br />

{<br />

btnUpload.Enabled = false;//Avoid upload twice<br />

//Init signals<br />

ManualResetEvent[] events = new ManualResetEvent[5];<br />

//Devide blocks<br />

int nTotalBytes = (int)( fi.Length / 5 );<br />

for( int i = 0; i < 5; i++ )<br />

{


events[i] = new ManualResetEvent( false );<br />

FileThread thdSub = new FileThread(<br />

i * nTotalBytes,<br />

( fi.Length - i * nTotalBytes ) > nTotalBytes ?<br />

nTotalBytes:(int)( fi.Length - i * nTotalBytes ),<br />

fi.FullName );<br />

ThreadPool.QueueUserWorkItem( new WaitCallback( thdSub.UploadFile ),<br />

events[i] );<br />

}<br />

//Wait for threads finished<br />

WaitHandle.WaitAll( events );<br />

}<br />

//Reset button status<br />

btnUpload.Enabled = true;<br />

总 体 来 说 , 程 序 还 是 相 对 比 较 简 单 , 而 我 也 只 是 做 了 个 简 单 例 子 而 已 , 一 些 细 节 都 没 有 进 行 处 理 。<br />

本 来 想 打 包 提 供 给 大 家 下 载 , 没 想 到 CSDN 的 Blog 对 于 这 点 做 的 太 差 , 老 是 异 常 。<br />

如 下 是 客 户 端 的 完 整 代 码 。<br />

//--------------------------- Multi-thread Upload Demo<br />

---------------------------------------<br />

//--------------------------------------------------------------------------------------------<br />

//---File: frmUpload<br />

//---Description: The multi-thread upload form file to demenstrate howto use<br />

multi-thread to<br />

// upload files<br />

//---Author: Knight<br />

//---Date: Oct.12, 2006<br />

//--------------------------------------------------------------------------------------------<br />

//---------------------------{Multi-thread Upload<br />

Demo}---------------------------------------<br />

using System;<br />

using System.Drawing;<br />

using System.Collections;<br />

using System.ComponentModel;<br />

using System.Windows.Forms;<br />

using System.Data;<br />

namespace CSUpload<br />

{<br />

using System.IO;<br />

using System.Diagnostics;


using System.Threading;<br />

using WSUploadFile;//Web-service reference namespace<br />

/// <br />

/// Summary description for Form1.<br />

/// <br />

public class frmUpload : System.Windows.Forms.Form<br />

{<br />

private System.Windows.Forms.TextBox txtFileName;<br />

private System.Windows.Forms.Button btnBrowse;<br />

private System.Windows.Forms.Button btnUpload;<br />

/// <br />

/// Required designer variable.<br />

/// <br />

private System.ComponentModel.Container components = null;<br />

public frmUpload()<br />

{<br />

//<br />

// Required for Windows Form Designer support<br />

//<br />

InitializeComponent();<br />

}<br />

//<br />

// TODO: Add any constructor code after InitializeComponent call<br />

//<br />

/// <br />

/// Clean up any resources being used.<br />

/// <br />

protected override void Dispose( bool disposing )<br />

{<br />

if( disposing )<br />

{<br />

if (components != null)<br />

{<br />

components.Dispose();<br />

}<br />

}<br />

base.Dispose( disposing );<br />

}<br />

#region Windows Form Designer generated code


/// Required method for Designer support - do not modify<br />

/// the contents of this method with the code editor.<br />

/// <br />

private void InitializeComponent()<br />

{<br />

this.txtFileName = new System.Windows.Forms.TextBox();<br />

this.btnBrowse = new System.Windows.Forms.Button();<br />

this.btnUpload = new System.Windows.Forms.Button();<br />

this.SuspendLayout();<br />

//<br />

// txtFileName<br />

//<br />

this.txtFileName.Location = new System.Drawing.Point(16, 24);<br />

this.txtFileName.Name = "txtFileName";<br />

this.txtFileName.Size = new System.Drawing.Size(248, 20);<br />

this.txtFileName.TabIndex = 0;<br />

this.txtFileName.Text = "";<br />

//<br />

// btnBrowse<br />

//<br />

this.btnBrowse.Location = new System.Drawing.Point(272, 24);<br />

this.btnBrowse.Name = "btnBrowse";<br />

this.btnBrowse.TabIndex = 1;<br />

this.btnBrowse.Text = "&Browse...";<br />

this.btnBrowse.Click += new<br />

System.EventHandler(this.btnBrowse_Click);<br />

//<br />

// btnUpload<br />

//<br />

this.btnUpload.Location = new System.Drawing.Point(272, 56);<br />

this.btnUpload.Name = "btnUpload";<br />

this.btnUpload.TabIndex = 2;<br />

this.btnUpload.Text = "&Upload";<br />

this.btnUpload.Click += new<br />

System.EventHandler(this.btnUpload_Click);<br />

//<br />

// frmUpload<br />

//<br />

this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);<br />

this.ClientSize = new System.Drawing.Size(370, 111);<br />

this.Controls.Add(this.btnUpload);<br />

this.Controls.Add(this.btnBrowse);<br />

this.Controls.Add(this.txtFileName);


this.FormBorderStyle =<br />

System.Windows.Forms.FormBorderStyle.FixedSingle;<br />

this.MaximizeBox = false;<br />

this.Name = "frmUpload";<br />

this.Text = "Upload";<br />

this.Load += new System.EventHandler(this.frmUpload_Load);<br />

this.ResumeLayout(false);<br />

}<br />

#endregion<br />

/// <br />

/// The main entry point for the application.<br />

/// <br />

static void Main()<br />

{<br />

Application.Run(new frmUpload());<br />

}<br />

private FileUpload myUpload = new FileUpload();<br />

private void UploadData( string FileName, int StartPos, byte[] bData )<br />

{<br />

//Call web service upload<br />

myUpload.UploadFileData( FileName, StartPos, bData );<br />

}<br />

private void btnUpload_Click(object sender, System.EventArgs e)<br />

{<br />

FileInfo fi = new FileInfo( txtFileName.Text );<br />

if( fi.Exists )<br />

{<br />

btnUpload.Enabled = false;//Avoid upload twice<br />

//Init signals<br />

ManualResetEvent[] events = new ManualResetEvent[5];<br />

//Devide blocks<br />

int nTotalBytes = (int)( fi.Length / 5 );<br />

for( int i = 0; i < 5; i++ )<br />

{<br />

events[i] = new ManualResetEvent( false );<br />

FileThread thdSub = new FileThread(<br />

i * nTotalBytes,<br />

( fi.Length - i * nTotalBytes ) > nTotalBytes ?


nTotalBytes:(int)( fi.Length - i * nTotalBytes ),<br />

fi.FullName );<br />

ThreadPool.QueueUserWorkItem( new WaitCallback(<br />

thdSub.UploadFile ), events[i] );<br />

}<br />

//Wait for threads finished<br />

WaitHandle.WaitAll( events );<br />

}<br />

//Reset button status<br />

btnUpload.Enabled = true;<br />

}<br />

private void frmUpload_Load(object sender, System.EventArgs e)<br />

{<br />

FileThread.UploadHandle = new UploadFileData( this.UploadData );<br />

}<br />

private void btnBrowse_Click(object sender, System.EventArgs e)<br />

{<br />

if( fileOpen.ShowDialog() == DialogResult.OK )<br />

txtFileName.Text = fileOpen.FileName;<br />

}<br />

private OpenFileDialog fileOpen = new OpenFileDialog();<br />

}<br />

public delegate void UploadFileData( string FileName, int StartPos, byte[]<br />

bData );<br />

/// <br />

/// FileThread: a class for sub-thread<br />

/// <br />

sealed class FileThread<br />

{<br />

private int nStartPos;<br />

private int nTotalBytes;<br />

private string strFileName;<br />

public static UploadFileData UploadHandle;<br />

///


Constructor<br />

/// <br />

/// <br />

/// <br />

/// <br />

public FileThread( int StartPos, int TotalBytes, string FileName )<br />

{<br />

//Init thread variant<br />

nStartPos = StartPos;<br />

nTotalBytes = TotalBytes;<br />

strFileName = FileName;<br />

byte:{2}",<br />

}<br />

//Only for debug<br />

Debug.WriteLine( string.Format( "File name:{0} position: {1} total<br />

strFileName, nStartPos, nTotalBytes ) );<br />

/// <br />

/// Sub-thread entry function<br />

/// <br />

/// <br />

public void UploadFile( object stateinfo )<br />

{<br />

int nRealRead, nBufferSize;<br />

const int BUFFER_SIZE = 10240;<br />

using( FileStream fs = new FileStream( strFileName,<br />

FileMode.Open, FileAccess.Read,<br />

FileShare.Read ) )<br />

{<br />

string sName = strFileName.Substring( strFileName.LastIndexOf(<br />

"\\" ) + 1 );<br />

byte[] bBuffer = new byte[BUFFER_SIZE];//Init 10k buffer<br />

fs.Position = nStartPos;<br />

nRealRead = 0;<br />

do<br />

{<br />

nBufferSize = BUFFER_SIZE;<br />

if( nRealRead + BUFFER_SIZE > nTotalBytes )<br />

nBufferSize = nTotalBytes - nRealRead;<br />

nBufferSize = fs.Read( bBuffer, 0, nBufferSize );<br />

if( nBufferSize == BUFFER_SIZE )


UploadHandle( sName,<br />

nRealRead + nStartPos,<br />

bBuffer );<br />

else if( nBufferSize > 0 )<br />

{<br />

//Copy data<br />

byte[] bytData = new byte[nBufferSize];<br />

Array.Copy( bBuffer,0, bytData, 0, nBufferSize );<br />

}<br />

UploadHandle( sName,<br />

nRealRead + nStartPos,<br />

bytData );<br />

}<br />

}<br />

nRealRead += nBufferSize;<br />

}<br />

while( nRealRead < nTotalBytes );<br />

}<br />

//Release signal<br />

ManualResetEvent mr = stateinfo as ManualResetEvent;<br />

if( mr != null )<br />

mr.Set();<br />

}<br />

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1332160<br />

[ 收 藏 到 我 的 网 摘 ] Knight94 发 表 于 2006 年 10 月 12 日 19:47:00<br />

相 关 文 章 :<br />

用 WebClient.UploadData 方 法 上 载 文 件 数 据 2005-04-17 sunsnow8<br />

<br />

# 小 玉 发 表 于 2006-10-26 09:36:00 IP: 218.24.228.*<br />

问 个 问 题 :<br />

水 晶 报 表 可 以 动 态 创 建 吗 ? 或 者<br />

.NET 里 什 么 报 表 可 以 实 现 动 态 创 建 报 表 ?<br />

我 现 在 要 做 在 程 序 动 行 时 设 计 报 表 , 可 以 给 我 些 提 示 吗<br />

谢 谢 了


# Night 发 表 于 2006-11-25 20:08:00 IP: 219.131.239.*<br />

请 问<br />

我 在 运 行 时 候<br />

这 句 //Wait for threads finished<br />

WaitHandle.WaitAll( events );<br />

出 现 报 错<br />

未 处 理 NotSupportedException<br />

不 支 持 一 个 STA 线 程 上 针 对 多 个 句 柄 的 WaitAll<br />

这 个 是 什 么 引 起 的<br />

但 是 注 释 了 这 句 也 可 以 运 行<br />

去 掉 受 不 受 影 响 ?<br />

# Night 发 表 于 2006-11-26 10:11:00 IP: 219.131.240.*<br />

你 好 !<br />

我 按 照 你 的 方 法 去 做 了<br />

去 掉 了 Program.cs 内 的 [STAThread]<br />

但 是 if (fileOpen.ShowDialog() == DialogResult.OK)<br />

这 里 显 示 错 误<br />

在 可 以 调 用 OLE 之 前 , 必 须 将 当 前 线 程 设 置 为 单 线 程 单 元 (STA) 模 式 。 请 确 保 您 的 Main 函 数 带 有<br />

STAThreadAttribute 标 记 。<br />

只 有 将 调 试 器 附 加 到 该 进 程 才 会 引 发 此 异 常 。<br />

这 种 该 怎 么 改 ?<br />

谢 谢<br />

# knight94 发 表 于 2006-11-26 10:55:00 IP: 218.71.239.*<br />

你 的 窗 体 上 引 用 了 什 么 特 殊 的 com。<br />

# knight94 发 表 于 2006-11-26 09:15:00 IP: 218.71.239.*<br />

to Night<br />

你 这 个 错 误 , 只 要 把 main 入 口 函 数 上 的 [STAThread] 属 性 标 示 去 掉 就 行 了 。<br />

# bj20082005 发 表 于 2007-01-23 17:11:56 IP:<br />

你 这 个 FileUpload myUpload = new FileUpload(); 是 引 用 哪 个 组 件 呀<br />

# FlyBird2004 发 表 于 2007-04-04 11:30:05 IP: 222.82.221.*<br />

可 以 将 这 个 程 序 的 打 包 帮 忙 发 一 份 嘛 ? 因 为 调 试 了 半 天 也 没 有 成 功 。 不 胜 感 激 !myg_mail@sina.com


新 手 老 问 题 --------- 跨 线 程 的 控 件 访 问<br />

电 子 科 技 大 学 03 级 02 班 周 银 辉<br />

新 手 经 常 会 遇 到 这 样 的 问 题 : a 线 程 去 访 问 b 线 程 的 控 件 , 编 译 器 报 错 (.net1.0 编 译 时 好 像 不 会 报 ,.net2.0 是 肯<br />

定 会 的 ).<br />

解 决 方 法 有 3 种 :<br />

1, 不 安 全 的 方 法 : 将 Control.CheckForIllegalCrossThreadCalls 设 置 为 false (.net1.0 中 没 有 )<br />

2, 安 全 的 方 法 : 异 步 委 托<br />

3, 安 全 的 方 法 : 就 是 使 用 BackgroundWorker 来 替 代 你 自 己 创 建 的 线 程 (.net1.0 中 没 有 )<br />

以 下 是 示 例 代 码<br />

using System;<br />

using System.ComponentModel;<br />

using System.Threading;<br />

using System.Windows.Forms;<br />

namespace CrossThreadDemo<br />

{<br />

public class Form1 : Form<br />

{<br />

// This delegate enables asynchronous calls for setting<br />

// the text property on a TextBox control.<br />

delegate void SetTextCallback(string text);<br />

// This thread is used to demonstrate both thread-safe and<br />

// unsafe ways to call a Windows Forms control.<br />

private Thread demoThread = null;<br />

// This BackgroundWorker is used to demonstrate the<br />

// preferred way of performing asynchronous operations.<br />

private BackgroundWorker backgroundWorker1;<br />

private TextBox textBox1;<br />

private Button setTextUnsafeBtn;<br />

private Button setTextSafeBtn;<br />

private Button setTextBackgroundWorkerBtn;<br />

private System.ComponentModel.IContainer components = null;<br />

public Form1()<br />

{<br />

InitializeComponent();<br />

}<br />

protected override void Dispose(bool disposing)


{<br />

}<br />

if (disposing && (components != null))<br />

{<br />

components.Dispose();<br />

}<br />

base.Dispose(disposing);<br />

// This event handler creates a thread that calls a<br />

// Windows Forms control in an unsafe way.<br />

private void setTextUnsafeBtn_Click(<br />

object sender,<br />

EventArgs e)<br />

{<br />

this.demoThread =<br />

new Thread(new ThreadStart(this.ThreadProcUnsafe));<br />

}<br />

this.demoThread.Start();<br />

// This method is executed on the worker thread and makes<br />

// an unsafe call on the TextBox control.<br />

private void ThreadProcUnsafe()<br />

{<br />

this.textBox1.Text = "This text was set unsafely.";<br />

}<br />

// This event handler creates a thread that calls a<br />

// Windows Forms control in a thread-safe way.<br />

private void setTextSafeBtn_Click(<br />

object sender,<br />

EventArgs e)<br />

{<br />

this.demoThread =<br />

new Thread(new ThreadStart(this.ThreadProcSafe));<br />

}<br />

this.demoThread.Start();<br />

// This method is executed on the worker thread and makes<br />

// a thread-safe call on the TextBox control.<br />

private void ThreadProcSafe()<br />

{<br />

this.SetText("This text was set safely.");


}<br />

// This method demonstrates a pattern for making thread-safe<br />

// calls on a Windows Forms control.<br />

//<br />

// If the calling thread is different from the thread that<br />

// created the TextBox control, this method creates a<br />

// SetTextCallback and calls itself asynchronously using the<br />

// Invoke method.<br />

//<br />

// If the calling thread is the same as the thread that created<br />

// the TextBox control, the Text property is set directly.<br />

private void SetText(string text)<br />

{<br />

// InvokeRequired required compares the thread ID of the<br />

// calling thread to the thread ID of the creating thread.<br />

// If these threads are different, it returns true.<br />

if (this.textBox1.InvokeRequired)<br />

{<br />

SetTextCallback d = new SetTextCallback(SetText);<br />

this.Invoke(d, new object[] { text });<br />

}<br />

else<br />

{<br />

this.textBox1.Text = text;<br />

}<br />

}<br />

// This event handler starts the form's<br />

// BackgroundWorker by calling RunWorkerAsync.<br />

//<br />

// The Text property of the TextBox control is set<br />

// when the BackgroundWorker raises the RunWorkerCompleted<br />

// event.<br />

private void setTextBackgroundWorkerBtn_Click(<br />

object sender,<br />

EventArgs e)<br />

{<br />

this.backgroundWorker1.RunWorkerAsync();<br />

}<br />

// This event handler sets the Text property of the TextBox<br />

// control. It is called on the thread that created the


TextBox control, so the call is thread-safe.<br />

//<br />

// BackgroundWorker is the preferred way to perform asynchronous<br />

// operations.<br />

private void backgroundWorker1_RunWorkerCompleted(<br />

object sender,<br />

RunWorkerCompletedEventArgs e)<br />

{<br />

this.textBox1.Text =<br />

"This text was set safely by BackgroundWorker.";<br />

}<br />

Windows Form Designer generated code#region Windows Form Designer generated code<br />

private void InitializeComponent()<br />

{<br />

this.textBox1 = new System.Windows.Forms.TextBox();<br />

this.setTextUnsafeBtn = new System.Windows.Forms.Button();<br />

this.setTextSafeBtn = new System.Windows.Forms.Button();<br />

this.setTextBackgroundWorkerBtn = new System.Windows.Forms.Button();<br />

this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker();<br />

this.SuspendLayout();<br />

//<br />

// textBox1<br />

//<br />

this.textBox1.Location = new System.Drawing.Point(12, 12);<br />

this.textBox1.Name = "textBox1";<br />

this.textBox1.Size = new System.Drawing.Size(240, 20);<br />

this.textBox1.TabIndex = 0;<br />

//<br />

// setTextUnsafeBtn<br />

//<br />

this.setTextUnsafeBtn.Location = new System.Drawing.Point(15, 55);<br />

this.setTextUnsafeBtn.Name = "setTextUnsafeBtn";<br />

this.setTextUnsafeBtn.TabIndex = 1;<br />

this.setTextUnsafeBtn.Text = "Unsafe Call";<br />

this.setTextUnsafeBtn.Click += new<br />

System.EventHandler(this.setTextUnsafeBtn_Click);<br />

//<br />

// setTextSafeBtn<br />

//<br />

this.setTextSafeBtn.Location = new System.Drawing.Point(96, 55);<br />

this.setTextSafeBtn.Name = "setTextSafeBtn";


this.setTextSafeBtn.TabIndex = 2;<br />

this.setTextSafeBtn.Text = "Safe Call";<br />

this.setTextSafeBtn.Click += new System.EventHandler(this.setTextSafeBtn_Click);<br />

//<br />

// setTextBackgroundWorkerBtn<br />

//<br />

this.setTextBackgroundWorkerBtn.Location = new System.Drawing.Point(177, 55);<br />

this.setTextBackgroundWorkerBtn.Name = "setTextBackgroundWorkerBtn";<br />

this.setTextBackgroundWorkerBtn.TabIndex = 3;<br />

this.setTextBackgroundWorkerBtn.Text = "Safe BW Call";<br />

this.setTextBackgroundWorkerBtn.Click += new<br />

System.EventHandler(this.setTextBackgroundWorkerBtn_Click);<br />

//<br />

// backgroundWorker1<br />

//<br />

this.backgroundWorker1.RunWorkerCompleted += new<br />

System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWork<br />

erCompleted);<br />

//<br />

// Form1<br />

//<br />

this.ClientSize = new System.Drawing.Size(268, 96);<br />

this.Controls.Add(this.setTextBackgroundWorkerBtn);<br />

this.Controls.Add(this.setTextSafeBtn);<br />

this.Controls.Add(this.setTextUnsafeBtn);<br />

this.Controls.Add(this.textBox1);<br />

this.Name = "Form1";<br />

this.Text = "Form1";<br />

this.ResumeLayout(false);<br />

this.PerformLayout();<br />

}<br />

#endregion<br />

[STAThread]<br />

static void Main()<br />

{<br />

Application.EnableVisualStyles();<br />

Application.Run(new Form1());<br />

}<br />

}<br />

}<br />

在 使 用 方 法 2 时 的 注 意 事 项 : 不 要 将 除 了 控 件 访 问 外 其 他 逻 辑 代 码 放 到 委 托 的 回 调 方 法 中 .


如 何 弹 出 一 个 模 式 窗 口 来 显 示 进 度 条<br />

最 近 看 了 好 多 人 问 这 方 面 的 问 题 , 以 前 我 也 写 过 一 篇 blog, 里 面 说 了 如 何 在 子 线 程 中 控 制 进 度 条 。 但 目 前 大 多<br />

数 环 境 , 需 要 弹 出 模 式 窗 口 , 来 显 示 进 度 条 , 那 么 只 需 要 在 原 先 的 基 础 上 稍 作 修 改 即 可 。<br />

首 先 是 进 度 条 窗 体 , 需 要 在 上 面 添 加 进 度 条 , 然 后 去 掉 ControlBox。 除 此 外 , 还 要 增 加 一 个 方 法 , 用 来 控 制 进<br />

度 条 的 增 加 幅 度 , 具 体 如 下 :<br />

/// <br />

/// Increase process bar<br />

/// <br />

/// the value increased<br />

/// <br />

public bool Increase( int nValue )<br />

{<br />

if( nValue > 0 )<br />

{<br />

if( prcBar.Value + nValue < prcBar.Maximum )<br />

{<br />

prcBar.Value += nValue;<br />

return true;<br />

}<br />

else<br />

{<br />

prcBar.Value = prcBar.Maximum;<br />

this.Close();<br />

return false;<br />

}<br />

}<br />

return false;<br />

}<br />

接 着 就 是 主 窗 体 了 , 如 何 进 行 操 作 了 , 首 先 需 要 定 义 两 个 私 有 成 员 , 一 个 委 托 。 其 中 一 个 私 有 成 员 是 保 存 当 前 进<br />

度 条 窗 体 对 象 , 另 一 个 是 保 存 委 托 方 法 ( 即 增 加 进 度 条 尺 度 ), 具 体 如 下 :<br />

private frmProcessBar myProcessBar = null;<br />

private delegate bool IncreaseHandle( int nValue );<br />

private IncreaseHandle myIncrease = null;<br />

接 着 要 在 主 窗 体 中 提 供 函 数 来 打 开 进 度 条 窗 体 , 如 下 :


/// Open process bar window<br />

/// <br />

private void ShowProcessBar()<br />

{<br />

myProcessBar = new frmProcessBar();<br />

// Init increase event<br />

myIncrease = new IncreaseHandle( myProcessBar.Increase );<br />

myProcessBar.ShowDialog();<br />

myProcessBar = null;<br />

}<br />

那 么 现 在 就 可 以 开 始 创 建 线 程 来 运 行 , 具 体 如 下 :<br />

/// <br />

/// Sub thread function<br />

/// <br />

private void ThreadFun()<br />

{<br />

MethodInvoker mi = new MethodInvoker( ShowProcessBar );<br />

this.BeginInvoke( mi );<br />

}<br />

Thread.Sleep( 1000 );//Sleep a while to show window<br />

bool blnIncreased = false;<br />

object objReturn = null;<br />

do<br />

{<br />

Thread.Sleep( 50 );<br />

objReturn = this.Invoke( this.myIncrease,<br />

new object[]{ 2 } );<br />

blnIncreased = (bool)objReturn ;<br />

}<br />

while( blnIncreased );<br />

注 意 以 上 , 在 打 开 进 度 条 窗 体 和 增 加 进 度 条 进 度 的 时 候 , 一 个 用 的 是 BeginInvoke, 一 个 是 Invoke,<br />

这 里 的 区 别 是 BeginInvoke 不 需 要 等 待 方 法 运 行 完 毕 , 而 Invoke 是 要 等 待 方 法 运 行 完 毕 。 还 有 一 点 , 此 处 用<br />

返 回 值 来 判 断 进 度 条 是 否 到 头 了 , 如 果 需 要 有 其 他 的 控 制 , 可 以 类 似 前 面 的 方 法 来 进 行 扩 展 。<br />

启 动 线 程 , 可 以 如 下 :<br />

Thread thdSub = new Thread( new ThreadStart( ThreadFun ) );<br />

thdSub.Start();<br />

这 样 , 一 个 用 模 式 打 开 进 度 条 窗 体 就 做 完 了 。


用 户 不 喜 欢 反 应 慢 的 程 序 。 在 执 行 耗 时 较 长 的 操 作 时 , 使 用 多 线 程 是 明 智 之 举 , 它 可 以 提 高 程 序 UI 的 响 应 速 度 ,<br />

使 得 一 切 运 行 显 得 更 为 快 速 。 在 Windows 中 进 行 多 线 程 编 程 曾 经 是 C++ 开 发 人 员 的 专 属 特 权 , 但 是 现 在 , 可<br />

以 使 用 所 有 兼 容 Microsoft .NET 的 语 言 来 编 写 。<br />

不 过 Windows 窗 体 体 系 结 构 对 线 程 使 用 制 定 了 严 格 的 规 则 。 如 果 只 是 编 写 单 线 程 应 用 程 序 , 则 没 必 要 知 道 这 些<br />

规 则 , 这 是 因 为 单 线 程 的 代 码 不 可 能 违 反 这 些 规 则 。 然 而 , 一 旦 采 用 多 线 程 , 就 需 要 理 解 Windows 窗 体 中 最 重<br />

要 的 一 条 线 程 规 则 : 除 了 极 少 数 的 例 外 情 况 , 否 则 都 不 要 在 它 的 创 建 线 程 以 外 的 线 程 中 使 用 控 件 的 任 何 成 员 。 本<br />

规 则 的 例 外 情 况 有 文 档 说 明 , 但 这 样 的 情 况 非 常 少 。 这 适 用 于 其 类 派 生 自 System.Windows.Forms.Control<br />

的 任 何 对 象 , 其 中 几 乎 包 括 UI 中 的 所 有 元 素 。 所 有 的 UI 元 素 ( 包 括 表 单 本 身 ) 都 是 从 Control 类 派 生 的 对 象 。<br />

此 外 , 这 条 规 则 的 结 果 是 一 个 被 包 含 的 控 件 ( 如 , 包 含 在 一 个 表 单 中 的 按 钮 ) 必 须 与 包 含 它 控 件 位 处 于 同 一 个 线<br />

程 中 。 也 就 是 说 , 一 个 窗 口 中 的 所 有 控 件 属 于 同 一 个 UI 线 程 。 实 际 中 , 大 部 分 Windows 窗 体 应 用 程 序 最 终 都<br />

只 有 一 个 线 程 , 所 有 UI 活 动 都 发 生 在 这 个 线 程 上 。 这 个 线 程 通 常 称 为 UI 线 程 。 这 意 味 着 您 不 能 调 用 用 户 界 面<br />

中 任 意 控 件 上 的 任 何 方 法 , 除 非 在 该 方 法 的 文 档 说 明 中 指 出 可 以 调 用 。 该 规 则 的 例 外 情 况 ( 总 有 文 档 记 录 ) 非 常<br />

少 而 且 它 们 之 间 关 系 也 不 大 。 请 注 意 , 以 下 代 码 是 非 法 的 :<br />

private Thread myThread;<br />

private void Form1_Load(object sender, EventArgs e)<br />

{<br />

myThread = new Thread(new ThreadStart(RunsOnWorkerThread));<br />

myThread.Start();<br />

}<br />

private void RunsOnWorkerThread()<br />

{<br />

label1.Text = "myThread 线 程 调 用 UI 控 件 ";<br />

}<br />

如 果 您 在 .NET Framework 1.0 版 本 中 尝 试 运 行 这 段 代 码 , 也 许 会 侥 幸 运 行 成 功 , 或 者 初 看 起 来 是 如 此 。 这 就<br />

是 多 线 程 错 误 中 的 主 要 问 题 , 即 它 们 并 不 会 立 即 显 现 出 来 。 甚 至 当 出 现 了 一 些 错 误 时 , 在 第 一 次 演 示 程 序 之 前 一<br />

切 看 起 来 也 都 很 正 常 。 但 不 要 搞 错 — 我 刚 才 显 示 的 这 段 代 码 明 显 违 反 了 规 则 , 并 且 可 以 预 见 , 任 何 抱 希 望 于 “ 试<br />

运 行 时 良 好 , 应 该 就 没 有 问 题 ” 的 人 在 即 将 到 来 的 调 试 期 是 会 付 出 沉 重 代 价 的 。<br />

下 面 我 们 来 看 看 有 哪 些 方 法 可 以 解 决 这 一 问 题 。<br />

一 、System.Windows.Forms.MethodInvoker 类 型 是 一 个 系 统 定 义 的 委 托 , 用 于 调 用 不 带 参 数 的 方 法 。


private Thread myThread;<br />

private void Form1_Load(object sender, EventArgs e)<br />

{<br />

myThread = new Thread(new ThreadStart(RunsOnWorkerThread));<br />

myThread.Start();<br />

}<br />

private void RunsOnWorkerThread()<br />

{<br />

MethodInvoker mi = new MethodInvoker(SetControlsProp);<br />

BeginInvoke(mi);<br />

}<br />

private void SetControlsProp()<br />

{<br />

label1.Text = "myThread 线 程 调 用 UI 控 件 ";<br />

}<br />

二 、 直 接 用 System.EventHandle( 可 带 参 数 )<br />

private Thread myThread;<br />

private void Form1_Load(object sender, EventArgs e)<br />

{<br />

myThread = new Thread(new ThreadStart(RunsOnWorkerThread));<br />

myThread.Start();<br />

}


private void RunsOnWorkerThread()<br />

{<br />

//DoSomethingSlow();<br />

string pList = "myThread 线 程 调 用 UI 控 件 ";<br />

label1.BeginInvoke(new System.EventHandler(UpdateUI), pList);<br />

}<br />

// 直 接 用 System.EventHandler, 没 有 必 要 自 定 义 委 托<br />

private void UpdateUI(object o, System.EventArgs e)<br />

{<br />

//UI 线 程 设 置 label1 属 性<br />

label1.Text = o.ToString() + " 成 功 !";<br />

}<br />

三 、 包 装 Control.Invoke<br />

虽 然 第 二 个 方 法 中 的 代 码 解 决 了 这 个 问 题 , 但 它 相 当 繁 琐 。 如 果 辅 助 线 程 希 望 在 结 束 时 提 供 更 多 的 反 馈 信 息 , 而<br />

不 是 简 单 地 给 出 “Finished!” 消 息 , 则 BeginInvoke 过 于 复 杂 的 使 用 方 法 会 令 人 生 畏 。 为 了 传 达 其 他 消 息 ,<br />

例 如 “ 正 在 处 理 ”、“ 一 切 顺 利 ” 等 等 , 需 要 设 法 向 UpdateUI 函 数 传 递 一 个 参 数 。 可 能 还 需 要 添 加 一 个 进 度 栏<br />

以 提 高 反 馈 能 力 。 这 么 多 次 调 用 BeginInvoke 可 能 导 致 辅 助 线 程 受 该 代 码 支 配 。 这 样 不 仅 会 造 成 不 便 , 而 且 考<br />

虑 到 辅 助 线 程 与 UI 的 协 调 性 , 这 样 设 计 也 不 好 。 对 这 些 进 行 分 析 之 后 , 我 们 认 为 包 装 函 数 可 以 解 决 这 两 个 问 题 。<br />

private Thread myThread;<br />

private void Form1_Load(object sender, EventArgs e)<br />

{<br />

myThread = new Thread(new ThreadStart(RunsOnWorkerThread));<br />

myThread.Start();<br />

}


private void RunsOnWorkerThread()<br />

{<br />

////DoSomethingSlow();<br />

for (int i = 0; i < 100; i++)<br />

{<br />

ShowProgress( Convert.ToString(i)+"%", i);<br />

Thread.Sleep(100);<br />

}<br />

}<br />

public void ShowProgress(string msg, int percentDone)<br />

{<br />

// Wrap the parameters in some EventArgs-derived custom class:<br />

System.EventArgs e = new MyProgressEvents(msg, percentDone);<br />

object[] pList = { this, e };<br />

BeginInvoke(new MyProgressEventsHandler(UpdateUI), pList);<br />

}<br />

private delegate void MyProgressEventsHandler(object sender, MyProgressEvents e);<br />

private void UpdateUI(object sender, MyProgressEvents e)<br />

{<br />

lblStatus.Text = e.Msg;<br />

myProgressControl.Value = e.PercentDone;


}<br />

public class MyProgressEvents : EventArgs<br />

{<br />

public string Msg;<br />

public int PercentDone;<br />

public MyProgressEvents(string msg, int per)<br />

{<br />

Msg = msg;<br />

PercentDone = per;<br />

}<br />

}<br />

ShowProgress 方 法 对 将 调 用 引 向 正 确 线 程 的 工 作 进 行 封 装 。 这 意 味 着 辅 助 线 程 代 码 不 再 担 心 需 要 过 多 关 注 UI<br />

细 节 , 而 只 要 定 期 调 用 ShowProgress 即 可 。<br />

如 果 我 提 供 一 个 设 计 为 可 从 任 何 线 程 调 用 的 公 共 方 法 , 则 完 全 有 可 能 某 人 会 从 UI 线 程 调 用 这 个 方 法 。 在 这 种 情<br />

况 下 , 没 必 要 调 用 BeginInvoke, 因 为 我 已 经 处 于 正 确 的 线 程 中 。 调 用 Invoke 完 全 是 浪 费 时 间 和 资 源 , 不 如<br />

直 接 调 用 适 当 的 方 法 。 为 了 避 免 这 种 情 况 ,Control 类 将 公 开 一 个 称 为 InvokeRequired 的 属 性 。 这 是 “ 只 限 UI<br />

线 程 ” 规 则 的 另 一 个 例 外 。 它 可 从 任 何 线 程 读 取 , 如 果 调 用 线 程 是 UI 线 程 , 则 返 回 假 , 其 他 线 程 则 返 回 真 。 这<br />

意 味 着 我 可 以 按 以 下 方 式 修 改 包 装 :<br />

public void ShowProgress(string msg, int percentDone)<br />

{<br />

if (InvokeRequired)<br />

{<br />

// As before<br />

//...


}<br />

else<br />

{<br />

// We're already on the UI thread just<br />

// call straight through.<br />

UpdateUI(this, new MyProgressEvents(msg,PercentDone));<br />

}<br />

}<br />

在 当 前 线 程 中 访 问 主 线 程 的 控 件<br />

在 做 广 州 移 动 集 团 短 信 平 台 的 时 候 , 需 要 定 时 刷 新 SP 的 告 警 信 息 。 于 是 创 建 了 一 个 Timer 对 象 , 在 Timer 对<br />

象 的 Elapsed 事 件 中 需 要 访 问 主 窗 体 中 的 一 个 LinkLabel。 刚 开 始 直 接 用 this.lnklblMsg.Text=" 你 有<br />

"+intMsg+" 条 新 告 警 信 息 "; 结 果 运 行 出 错 :System.InvalidOperationException: 线 程 间 操 作 无 效 : 从 不 是<br />

创 建 控 件 “lnklblMsg” 的 线 程 访 问 它 。 主 要 原 因 是 控 件 “lnklblMsg” 是 由 主 窗 体 创 建 的 , 而 访 问 它 是 在 Timer<br />

创 建 的 线 程 中 访 问 。 解 决 这 个 问 题 的 方 法 是 用 Invoke 来 执 行 委 托 , 间 接 调 用 lnklblMsg 控 件 。<br />

首 先 创 建 委 托 和 访 问 LinkLabel 控 件 的 方 法 :<br />

// 创 建 委 托 , 用 于 访 问 主 线 程 的 控 件<br />

public delegate void delegateNewMsg(int msgs);<br />

void dgtNewMsgMethod(int intMsgs)<br />

{<br />

if (intMsgs > 0)<br />

{<br />

this.lnklblNewWarn.Text = " 有 " + intMsgs.ToString() + " 条 新 告 警 信 息 ";<br />

}<br />

else<br />

{<br />

this.lnklblNewWarn.Text = "";<br />

}<br />

}<br />

然 后 再 在 Timer 的 Elapsed 方 法 中 执 行 委 托 :<br />

delegateNewMsg d = dgtNewMsgMethod;


Invoke(d,intMsgs);// 访 问 主 线 程 资 源<br />

C# 中 在 线 程 中 访 问 主 Form 控 件 的 问 题<br />

C# 不 允 许 直 接 从 线 程 中 访 问 Form 里 的 控 件 , 比 如 希 望 在 线 程 里 修 改 Form 里 的 一 个 TextBox 的 内 容 等 等 ,<br />

唯 一 的 做 法 是 使 用 Invoke 方 法 , 下 面 是 一 个 MSDN 里 的 Example, 很 说 明 问 题 :<br />

using System;<br />

using System.Drawing;<br />

using System.Windows.Forms;<br />

using System.Threading;<br />

public class MyFormControl : Form<br />

...{<br />

public delegate void AddListItem(String myString);<br />

public AddListItem myDelegate;<br />

private Button myButton;<br />

private Thread myThread;<br />

private ListBox myListBox;<br />

public MyFormControl()<br />

...{<br />

myButton = new Button();<br />

myListBox = new ListBox();<br />

myButton.Location = new Point(72, 160);<br />

myButton.Size = new Size(152, 32);<br />

myButton.TabIndex = 1;<br />

myButton.Text = "Add items in list box";<br />

myButton.Click += new EventHandler(Button_Click);<br />

myListBox.Location = new Point(48, 32);<br />

myListBox.Name = "myListBox";<br />

myListBox.Size = new Size(200, 95);<br />

myListBox.TabIndex = 2;<br />

ClientSize = new Size(292, 273);<br />

Controls.AddRange(new Control[] ...{myListBox,myButton});<br />

Text = " 'Control_Invoke' example ";<br />

myDelegate = new AddListItem(AddListItemMethod);<br />

}<br />

static void Main()<br />

...{<br />

MyFormControl myForm = new MyFormControl();<br />

myForm.ShowDialog();<br />

}<br />

public void AddListItemMethod(String myString)


...{<br />

myListBox.Items.Add(myString);<br />

}<br />

private void Button_Click(object sender, EventArgs e)<br />

...{<br />

myThread = new Thread(new ThreadStart(ThreadFunction));<br />

myThread.Start();<br />

}<br />

private void ThreadFunction()<br />

...{<br />

MyThreadClass myThreadClassObject = new MyThreadClass(this);<br />

myThreadClassObject.Run();<br />

}<br />

}<br />

public class MyThreadClass<br />

...{<br />

MyFormControl myFormControl1;<br />

public MyThreadClass(MyFormControl myForm)<br />

...{<br />

myFormControl1 = myForm;<br />

}<br />

String myString;<br />

public void Run()<br />

...{<br />

}<br />

}<br />

for (int i = 1; i


如 何 在 子 线 程 中 操 作 窗 体 上 的 控 件 - 愚 翁 专 栏 - CSDNBlog 正 在 处 理 您 的 请 求 ...<br />

如 何 在 子 线 程 中 操 作 窗 体 上 的 控 件<br />

一 般 来 说 , 直 接 在 子 线 程 中 对 窗 体 上 的 控 件 操 作 是 会 出 现 异 常 , 这 是 由 于 子 线 程 和 运 行 窗 体 的 线 程 是 不 同 的 空 间 ,<br />

因 此 想 要 在 子 线 程 来 操 作 窗 体 上 的 控 件 , 是 不 可 能 简 单 的 通 过 控 件 对 象 名 来 操 作 , 但 不 是 说 不 能 进 行 操 作 , 微 软<br />

提 供 了 Invoke 的 方 法 , 其 作 用 就 是 让 子 线 程 告 诉 窗 体 线 程 来 完 成 相 应 的 控 件 操 作 。<br />

现 在 用 一 个 用 线 程 控 制 的 进 程 条 来 说 明 , 大 致 的 步 骤 如 下 :<br />

1. 创 建 Invoke 函 数 , 大 致 如 下 :<br />

/// <br />

/// Delegate function to be invoked by main thread<br />

/// <br />

private void InvokeFun()<br />

{<br />

if( prgBar.Value < 100 )<br />

prgBar.Value = prgBar.Value + 1;<br />

}<br />

2. 子 线 程 入 口 函 数 :<br />

/// <br />

/// Thread function interface<br />

/// <br />

private void ThreadFun()<br />

{<br />

//Create invoke method by specific function<br />

MethodInvoker mi = new MethodInvoker( this.InvokeFun );<br />

}<br />

for( int i = 0; i < 100; i++ )<br />

{<br />

this.BeginInvoke( mi );<br />

Thread.Sleep( 100 );<br />

}<br />

3. 创 建 子 线 程 :<br />

Thread thdProcess = new Thread( new ThreadStart( ThreadFun ) );<br />

thdProcess.Start();<br />

备 注 :<br />

using System.Threading;<br />

private System.Windows.Forms.ProgressBar prgBar;<br />

运 行 后 的 效 果 如 下 图 所 示 :


Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=626584<br />

[ 收 藏 到 我 的 网 摘 ] Knight94 发 表 于 2006 年 03 月 16 日 19:25:00<br />


# DisonWorld 发 表 于 2006-05-30 13:51:00 IP: 61.186.185.*<br />

I have a question as the following:<br />

foreach(int i in ...)<br />

{<br />

//the order is important, that is:<br />

//DoSomthing(2) must be done after the DoSomething(1) is completed<br />

DoSomething(i);<br />

}<br />

private void DoSomething(int i)<br />

{<br />

//In here, a function to upload file will be called,<br />

//and it will be implemeted by using class Upload, who is inherited from<br />

BackgroundWorker and will call a WSE<br />

// to upload the file, so the speed is a bit slow<br />

}<br />

So my question is how to make the DoSomthing(2) only begins when the<br />

DoSomthing(1) is finished.<br />

# knight94 发 表 于 2006-05-30 14:04:00 IP: 218.71.239.*<br />

to DisonWorld<br />

you can reprace "BeginInvoke" method with "Invoke" method.<br />

See difference between them in:<br />

http://blog.csdn.net/Knight94/archive/2006/05/27/757351.aspx<br />

# DisonWorld 发 表 于 2006-05-31 12:00:00 IP: 61.186.185.*<br />

Hello Knight94,<br />

I have tried the Invoke, but the ui seem to be dead, and the import order<br />

seems to wrong.<br />

For example:<br />

PackageA: no files to import<br />

PackageB: need to import files<br />

PackageC: no files to import<br />

After the PackageA is finished, and the PackageB will start and begin<br />

import file, but even the PackageB has not been finished, the PackageC<br />

will start.<br />

private delegate bool ImportSinglePackage();<br />

private ImportSinglePackage importSinglePackage = null;


The UI will call the BeginImport to start the importing<br />

public BeginImport()<br />

{<br />

Cursor.Current = Cursors.WaitCursor;<br />

this.importSinglePackage = new<br />

ImportSinglePackage(this.ImportBySinglePackage);<br />

mImportingDataThread = new Thread(new<br />

ThreadStart(this.ImportPackages));<br />

mImportingDataThread.Start();<br />

}<br />

private void ImportPackages()<br />

{<br />

bool IsAllPackagesHaveBeenImported = false;<br />

do{<br />

this.currentImportRecordKey =<br />

recordKeysToImport[recordsHasBeenImported];<br />

Thread.Sleep(1000);<br />

IsAllPackagesHaveBeenImported =<br />

(bool)this.Invoke(this.importSinglePackage, null);<br />

}while (!IsAllPackagesHaveBeenImported);<br />

}<br />

private bool ImportBySinglePackage()<br />

{<br />

//ImportFiles will call the a class inherited from the BackgroundWorker to<br />

download/upload file<br />

Thread threadImportFiles = new Thread(new ThreadStart(this.ImportFiles));<br />

threadImportFiles.Start();<br />

threadImportFiles.Join();<br />

# knight94 发 表 于 2006-05-31 13:09:00 IP: 218.71.239.*<br />

As the upper code, there is no return value in your function named<br />

"ImportBySinglePackage".<br />

By the way, you should check the invoke's return value in your<br />

"ImportPackages" function.<br />

# tianjj 发 表 于 2006-08-17 17:10:00 IP: 203.86.72.*<br />

to:knight94<br />

--------in thread fun ----------


MethodInvoker mi = new MethodInvoker( this.ShowText );<br />

这 个 调 用 带 有 参 数 , 好 像 有 误 。<br />

提 示 showText 重 载 与 MethodInvoker 不 匹 配 。<br />

# tianjj 发 表 于 2006-08-17 17:12:00 IP: 203.86.72.*<br />

to:knight94<br />

--------in thread fun ----------<br />

MethodInvoker mi = new MethodInvoker( this.ShowText );<br />

这 个 调 用 带 有 参 数 , 好 像 有 误 。<br />

提 示 showText 重 载 与 MethodInvoker 不 匹 配 。<br />

# tianjj 发 表 于 2006-08-17 17:27:00 IP: 203.86.72.*<br />

to:knight94<br />

MethodInvoker mi = new MethodInvoker( this.ShowText );<br />

这 个 调 用 带 参 数 方 法 , 好 像 有 误 ,<br />

显 示 showText 重 载 与 MethodInvoker 不 匹 配 。<br />

# knight94 发 表 于 2006-08-17 18:07:00 IP: 218.71.239.*<br />

to tianjj<br />

参 看 :<br />

http://blog.csdn.net/knight94/archive/2006/05/27/757351.aspx<br />

# knight94 发 表 于 2006-08-17 18:10:00 IP: 218.71.239.*<br />

to tianjj<br />

MethodInvoker 是 一 个 delegate 类 型 , 你 可 以 自 己 定 义 , 例 如 :<br />

public delegate int MyInvoker( string strValue );<br />

MyInvoder mi = new MyInvoker( yourMethod );<br />

this.Invoke( mi, new object[]{ "test" } );<br />

# 菜 鸟 发 表 于 2006-08-23 16:29:00 IP: 219.140.184.*<br />

进 度 条 为 什 么 不 用 TIMER 控 件 来 做 ?<br />

# knight94 发 表 于 2006-08-23 17:48:00 IP: 218.71.239.*<br />

Timer 的 交 互 性 太 差 。<br />

# 大 漠 狂 沙 发 表 于 2006-11-02 14:00:00 IP: 219.142.3.*<br />

工 作 线 程 类 :<br />

public class CommissionHelper<br />

{


private System.Windows.Forms.ProgressBar proBar;<br />

public CommissionHelper(System.Windows.Forms.ProgressBar proBar)<br />

{<br />

this.proBar=proBar;<br />

}<br />

public void Run()<br />

{<br />

int i=1;<br />

for()<br />

{<br />

proBar.Value=i;<br />

i=i+1;<br />

}<br />

}<br />

}<br />

主 线 程 窗 体 按 钮 调 用 :<br />

private void btnTransfer_Click(object sender, System.EventArgs e)<br />

{<br />

CommissionHelper helper=new CommissionHelper(proBar);<br />

Thread tdh=new Thread( new ThreadStart( helper.Run ) );<br />

tdh.Start();<br />

}<br />

这 样 也 可 以 实 现 。 我 没 明 白 为 什 么 要 用 Invoke?!<br />

谢 谢 !<br />

# knight94 发 表 于 2006-11-02 14:49:00 IP: 218.71.239.*<br />

to 大 漠 狂 沙<br />

问 题 是 很 对 情 况 下 , 在 子 线 程 中 不 能 直 接 操 纵 UI 线 程 上 的 控 件 , 这 时 候 就 需 要 用 Invoke<br />

或 者 BeginInvoke 来 完 成 。<br />

也 就 是 说 现 在 有 些 程 序 是 可 以 跨 线 程 互 相 操 作 , 有 些 程 序 是 不 允 许 的 。<br />

前 者 , 按 照 你 所 说 的 没 问 题 ; 后 者 按 照 你 所 写 的 , 则 是 有 问 题 的 。<br />

# 大 漠 狂 沙 发 表 于 2006-11-03 11:16:00 IP: 219.142.3.*<br />

嗯 , 明 白 了 , 谢 谢 你 的 回 复 , 学 到 很 多 东 西 !<br />

# 小 鲁 发 表 于 2006-11-04 14:16:00 IP: 58.61.133.*<br />

愚 翁 老 师 , 非 常 感 谢 , 我 钻 研 了 阻 塞 线 程 中 打 开 新 窗 体 , 窗 体 定 死 的 问 题 , 一 天 一 夜 都 没 有 答<br />

案 , 你 的 文 章 个 给 了 我 启 发 , 现 在 解 决 了 非 常 感 谢 .


# wc_king 发 表 于 2006-11-06 00:44:00 IP: 222.90.59.*<br />

knight94 发 表 于 2006-11-02 14:49:00 IP: 218.71.239.*<br />

to 大 漠 狂 沙<br />

问 题 是 很 对 情 况 下 , 在 子 线 程 中 不 能 直 接 操 纵 UI 线 程 上 的 控 件 , 这 时 候 就 需 要 用 Invoke<br />

或 者 BeginInvoke 来 完 成 。<br />

也 就 是 说 现 在 有 些 程 序 是 可 以 跨 线 程 互 相 操 作 , 有 些 程 序 是 不 允 许 的 。<br />

前 者 , 按 照 你 所 说 的 没 问 题 ; 后 者 按 照 你 所 写 的 , 则 是 有 问 题 的 。<br />

--------------------------<br />

请 问 , 能 举 个 例 子 说 明 什 么 时 候 会 出 现 跨 线 程 互 相 操 作 有 问 题 ? 那 么 应 该 根 据 什 么 标 准 来<br />

判 断 会 出 现 这 种 问 题 ?<br />

# knight94 发 表 于 2006-11-06 17:56:00 IP: 218.71.239.*<br />

to wc_king<br />

对 于 你 所 说 的 , 可 以 通 过 Control.InvokeRequired 来 进 行 判 断 是 否 需 要 Invoke 或 者<br />

BeginInvoke 来 操 作 。<br />

# 老 实 和 尚 发 表 于 2006-11-24 14:25:00 IP: 211.162.77.*<br />

楼 主 :<br />

我 新 学 C#, 以 前 一 直 在 unix 下 面 混 的 , 现 在 我 根 据 您 的 介 绍 写 一 个 界 面 , 需 求 是 : 当<br />

我 按 了 “begin”button 以 后 ,listview 上 不 停 的 打 印 东 西 出 来 , 按 了 “stop”button 以 后 ,<br />

停 止 打 印 。 源 代 码 如 下 所 示 :<br />

using System.Collections.Generic;<br />

using System.ComponentModel;<br />

using System.Data;<br />

using System.Drawing;<br />

using System.Text;<br />

using System.Windows.Forms;<br />

using System.Threading;<br />

namespace WindowsApplication1<br />

{<br />

public partial class Form1 : Form<br />

{<br />

private volatile bool flag = false;<br />

private Thread th1 = null;<br />

public Form1()<br />

{<br />

InitializeComponent();<br />

}


private void button1_Click(object sender, EventArgs e)<br />

{<br />

flag = true;<br />

th1 = new Thread(new ThreadStart(th_fun));<br />

th1.Start();<br />

}<br />

private void fun()<br />

{<br />

while (flag)<br />

{<br />

listView1.Items.Add("ssss");<br />

listView1.Refresh();<br />

Thread.Sleep(1000);<br />

}<br />

Console.WriteLine("stopppppp");<br />

}<br />

private void th_fun()<br />

{<br />

MethodInvoker me = new MethodInvoker(this.fun);<br />

this.BeginInvoke(me);<br />

}<br />

private void button2_Click(object sender, EventArgs e)<br />

{<br />

Console.WriteLine("stop clicked");<br />

# knight94 发 表 于 2006-11-24 16:58:00 IP: 218.71.239.*<br />

to 老 实 和 尚<br />

你 的 循 环 放 错 位 置 了 , 你 这 样 写 , 也 就 是 线 程 只 执 行 一 次 函 数 , 而 这 个 函 数 永 远 在 执 行 。<br />

你 如 下 去 修 改<br />

private void fun()<br />

{<br />

listView1.Items.Add("ssss");<br />

listView1.Refresh();<br />

}<br />

private void th_fun()<br />

{<br />

MethodInvoker me = new MethodInvoker(this.fun);<br />

while (flag)


{<br />

this.BeginInvoke(me);<br />

Thread.Sleep(1000);<br />

}<br />

Console.WriteLine("stopppppp");<br />

}<br />

# 老 实 和 尚 发 表 于 2006-11-24 17:37:00 IP: 211.162.77.*<br />

谢 谢 楼 主 。<br />

thx~~~~<br />

# atealxt 发 表 于 2006-12-12 11:31:06 IP: 221.216.0.*<br />

谢 谢 你 的 几 篇 Thread 的 文 章 让 我 解 决 了 和 上 面 " 小 鲁 " 一 样 的 问 题<br />

# dingsea 发 表 于 2007-01-09 16:50:37 IP: 219.74.131.*<br />

如 果 把 InvokeFun() 的 实 现 改 为<br />

label1.Text+="1";<br />

这 样 一 句 的 话 , 在 程 序 没 有 运 行 完 之 前 退 出 会 有 异 常 :<br />

Cannot call Invoke or InvokeAsync on a control until the window handle has<br />

been created.<br />

# superwat 发 表 于 2007-01-19 16:01:17 IP: 218.104.7.*<br />

老 大 , 我 郁 闷 了 , 救 救 我 啊 .<br />

using System.Threading;<br />

private Boolean bTasking = false;<br />

private Thread tr_Task;<br />

public void UF_Task(Boolean bstart)<br />

{<br />

bTasking = bstart;<br />

if (bstart) sBarP2.Value = 0;<br />

if (tr_Task == null)<br />

{<br />

tr_Task = new Thread(new ThreadStart(UF_DoProcess));<br />

tr_Task.IsBackground = true;<br />

tr_Task.Name = "QBSProcess";<br />

tr_Task.Start();<br />

}<br />

if (!bstart) sBarP2.Value = 100;<br />

}<br />

private void UF_DoProcess()


{<br />

try<br />

{<br />

MethodInvoker mip = new MethodInvoker(this.UF_ProcStep);<br />

while (bTasking)<br />

{<br />

this.BeginInvoke(mip);<br />

Thread.Sleep(100);<br />

}<br />

}<br />

catch { }<br />

}<br />

private void UF_ProcStep()<br />

{<br />

if (!bTasking) return;<br />

if (sBarP2.Value == 100) sBarP2.Value = 0;<br />

else sBarP2.PerformStep();<br />

}<br />

假 设 我 在 调 用 一 个 任 务 A 时 , 大 约 需 要 1 秒 , 调 用<br />

UF_Task(true);<br />

A 任 务 ;<br />

UF_Task(false);<br />

可 是 进 度 条 没 有 中 间 状 态 啊 , 进 度 条 只 有 0 和 100....<br />

怎 么 回 事 啊 ???<br />

在 线 等 你 , 老 大 .<br />

# Knight94 发 表 于 2007-01-20 11:40:05 IP: 210.77.27.*<br />

to superwat<br />

你 出 现 的 问 题 是 由 于 用 Invoke 是 提 交 给 UI 所 在 线 程 来 完 成 , 而 UI 线 程 此 时 忙 于 处 理 A<br />

任 务 , 因 此 得 不 到 及 时 响 应 。<br />

因 此 你 可 以 把 这 部 分<br />

UF_Task(true);<br />

A 任 务 ;<br />

UF_Task(false);<br />

放 到 一 个 单 独 的 线 程 去 处 理 , 这 样 就 避 免 了 UI 线 程 被 占 用 。<br />

# superwat 发 表 于 2007-01-22 16:33:00 IP: 218.104.7.*<br />

:)<br />

谢 谢 老 大 , 我 也 明 白 其 中 的 道 理 了 . 现 在 想 到 另 外 一 个 办 法 . 我 试 试 效 果 , 行 的 通 的 话 , 回 来


献 丑 , 呵 呵<br />

# sunrobust 发 表 于 2007-01-26 11:50:23 IP: 221.122.45.*<br />

愚 翁 , 您 好 ! 这 篇 文 章 使 我 收 益 匪 浅 , 致 谢 !<br />

我 是 新 手 , 有 几 个 问 题 向 你 咨 询 .<br />

1. 如 果 我 将 子 线 程 单 独 写 在 一 个 类 定 义 里 面 , 应 如 何 安 排 InvokeFun() 和<br />

ThreadFun() 两 个 函 数 ?<br />

即 谁 在 子 线 程 定 义 中 , 谁 应 该 写 在 主 窗 体 定 义 中 ?<br />

2. 是 不 是 在 子 线 程 中 仍 然 要 引 入 System.Windows.Forms 命 名 控 件 ?<br />

# sunrobust 发 表 于 2007-01-26 11:51:31 IP: 221.122.45.*<br />

3. 是 否 要 修 改 ProcessBar 的 属 性 为 Public?<br />

# Knight94 发 表 于 2007-01-31 10:13:11 IP: 210.77.27.*<br />

to sunrobust<br />

不 一 定 , 既 然 是 通 过 委 托 来 操 作 , 没 必 要 把 processbar 属 性 设 为 public, 只 需 要 给 出<br />

相 应 的 修 改 方 法 就 行 。<br />

你 可 以 参 看 这 篇 文 章<br />

http://blog.csdn.net/Knight94/archive/2006/08/24/1111267.aspx


委 托<br />

[ 转 ] 如 何 智 能 客 户 端 应 用 程 序 性 能<br />

智 能 客 户 端 应 用 程 序 性 能<br />

发 布 日 期 : 08/20/2004 | 更 新 日 期 : 08/20/2004<br />

智 能 客 户 端 体 系 结 构 与 设 计 指 南<br />

David Hill、Brenton Webster、Edward A. Jezierski、Srinath Vasireddy、Mohamma<br />

d Al-Sabt,Microsoft Corporation,Blaine Wastell Ascentium Corporation,Jonath<br />

an Rasmusson 和 Paul Gale ThoughtWorks 和 Paul Slater Wadeware LLC<br />

相 关 链 接<br />

Microsoft® patterns & practices 库 http://www.microsoft.com/resources/practice<br />

s/default.mspx<br />

.NET 的 应 用 程 序 体 系 结 构 : 设 计 应 用 程 序 和 服 务 http://msdn.microsoft.com/library/enus/dnbda/html/distapp.asp<br />

摘 要 : 本 章 讨 论 如 何 优 化 您 的 智 能 客 户 端 应 用 程 序 。 本 章 分 析 您 可 以 在 设 计 时 采 取 的 步 骤 , 并 介<br />

绍 如 何 调 整 智 能 客 户 端 应 用 程 序 以 及 诊 断 任 何 性 能 问 题 。


5H<br />

7H<br />

9H<br />

本 页 内 容<br />

4 针 对 性 能 进 行 设 计<br />

6H 性 能 调 整 和 诊 断<br />

8H 小 结<br />

10H 参 考 资 料<br />

智 能 客 户 端 应 用 程 序 可 以 提 供 比 Web 应 用 程 序 更 丰 富 和 响 应 速 度 更 快 的 用 户 界 面 , 并 且 可 以<br />

利 用 本 地 系 统 资 源 。 如 果 应 用 程 序 的 大 部 分 驻 留 在 用 户 的 计 算 机 上 , 则 应 用 程 序 不 需 要 到 Web<br />

服 务 器 的 持 续 的 往 返 行 程 。 这 有 利 于 提 高 性 能 和 响 应 性 。 然 而 , 要 实 现 智 能 客 户 端 应 用 程 序 的<br />

全 部 潜 能 , 您 应 该 在 应 用 程 序 的 设 计 阶 段 仔 细 考 虑 性 能 问 题 。 通 过 在 规 划 和 设 计 您 的 应 用 程 序 时<br />

解 决 性 能 问 题 , 可 以 帮 助 您 及 早 控 制 成 本 , 并 减 小 以 后 陷 入 性 能 问 题 的 可 能 性 。<br />

注 改 善 智 能 客 户 端 应 用 程 序 的 性 能 并 不 仅 限 于 应 用 程 序 设 计 问 题 。 您 可 以 在 整 个 应 用 程 序 生 存 期<br />

中 采 取 许 多 个 步 骤 来 使 .NET 代 码 具 有 更 高 的 性 能 。 虽 然 .NET 公 共 语 言 运 行 库 (CLR) 在 执<br />

行 代 码 方 面 非 常 有 效 , 但 您 可 以 使 用 多 种 技 术 来 提 高 代 码 的 性 能 , 并 防 止 在 代 码 级 引 入 性 能 问 题 。<br />

有 关 这 些 问 题 的 详 细 信 息 , 请 参 阅 11Hhttp://msdn.microsoft.com/perf。<br />

在 应 用 程 序 的 设 计 中 定 义 现 实 的 性 能 要 求 并 识 别 潜 在 的 问 题 显 然 是 重 要 的 , 但 是 性 能 问 题 通 常 只<br />

在 编 写 代 码 之 后 对 其 进 行 测 试 时 出 现 。 在 这 种 情 况 下 , 您 可 以 使 用 一 些 工 具 和 技 术 来 跟 踪 性 能 问<br />

题 。<br />

本 章 分 析 如 何 设 计 和 调 整 您 的 智 能 客 户 端 应 用 程 序 以 获 得 最 佳 性 能 。 它 讨 论 了 许 多 设 计 和 体 系 结<br />

构 问 题 ( 包 括 线 程 处 理 和 缓 存 注 意 事 项 ), 并 且 分 析 了 如 何 增 强 应 用 程 序 的 Windows 窗 体 部<br />

分 的 性 能 。 本 章 还 介 绍 了 您 可 以 用 来 跟 踪 和 诊 断 智 能 客 户 端 应 用 程 序 性 能 问 题 的 一 些 技 术 和 工<br />

具 。<br />

针 对 性 能 进 行 设 计


您 可 以 在 应 用 程 序 设 计 或 体 系 结 构 级 完 成 许 多 工 作 , 以 确 保 智 能 客 户 端 应 用 程 序 具 有 良 好 的 性<br />

能 。 您 应 该 确 保 在 设 计 阶 段 尽 可 能 早 地 制 定 现 实 的 且 可 度 量 的 性 能 目 标 , 以 便 评 估 设 计 折 衷 , 并<br />

且 提 供 最 划 算 的 方 法 来 解 决 性 能 问 题 。 只 要 可 能 , 性 能 目 标 就 应 该 基 于 实 际 的 用 户 和 业 务 要 求 ,<br />

因 为 这 些 要 求 受 到 应 用 程 序 所 处 的 操 作 环 境 的 强 烈 影 响 。 性 能 建 模 是 一 种 结 构 化 的 且 可 重 复 的 过<br />

程 , 您 可 以 使 用 该 过 程 来 管 理 您 的 应 用 程 序 并 确 保 其 实 现 性 能 目 标 。 有 关 详 细 信 息 , 请 参 阅 I<br />

mproving .NET Application Performance and Scalability 中 的 第 2 章 “Performance<br />

Modeling”, 网 址 为 :12Hhttp://msdn.microsoft.com/library/default.asp?url=/library/e<br />

n-us/dnpag/html/scalenetchapt02.asp。<br />

智 能 客 户 端 通 常 是 较 大 的 分 布 式 应 用 程 序 的 组 成 部 分 。 很 重 要 的 一 点 是 在 完 整 应 用 程 序 的 上 下 文<br />

中 考 虑 智 能 客 户 端 应 用 程 序 的 性 能 , 包 括 该 客 户 端 应 用 程 序 使 用 的 所 有 位 于 网 络 中 的 资 源 。 微 调<br />

并 优 化 应 用 程 序 中 的 每 一 个 组 件 通 常 是 不 必 要 或 不 可 能 的 。 相 反 , 性 能 调 整 应 该 基 于 优 先 级 、 时<br />

间 、 预 算 约 束 和 风 险 。 一 味 地 追 求 高 性 能 通 常 并 不 是 一 种 划 算 的 策 略 。<br />

智 能 客 户 端 还 将 需 要 与 用 户 计 算 机 上 的 其 他 应 用 程 序 共 存 。 当 您 设 计 智 能 客 户 端 应 用 程 序 时 , 您<br />

应 该 考 虑 到 您 的 应 用 程 序 将 需 要 与 客 户 端 计 算 机 上 的 其 他 应 用 程 序 共 享 系 统 资 源 , 例 如 , 内 存 、<br />

CPU 时 间 和 网 络 利 用 率 。<br />

注 有 关 设 计 可 伸 缩 的 高 性 能 远 程 服 务 的 信 息 , 请 参 阅 Improving .NET Performance and S<br />

calability, 网 址 为 :13Hhttp://msdn.microsoft.com/library/default.asp?url=/library/enus/dnpag/html/scalenet.asp。<br />

本 指 南 包 含 有 关 如 何 优 化 .NET 代 码 以 获 得 最 佳 性 能 的 详 细<br />

信 息 。<br />

要 设 计 高 性 能 的 智 能 客 户 端 , 请 考 虑 下 列 事 项 :<br />

• 在 适 当 的 位 置 缓 存 数 据 。 数 据 缓 存 可 以 显 著 改 善 智 能 客 户 端 应 用 程 序 的 性 能 , 使 您 可 以 在 本 地 使 用 数 据 ,<br />

而 不 必 经 常 从 网 络 检 索 数 据 。 但 是 , 敏 感 数 据 或 频 繁 更 改 的 数 据 通 常 不 适 合 进 行 缓 存 。<br />

• 优 化 网 络 通 讯 。 如 果 通 过 “ 健 谈 的 ” 接 口 与 远 程 层 服 务 进 行 通 讯 , 并 且 借 助 于 多 个 请 求 / 响 应 往 返 行 程 来 执<br />

行 单 个 逻 辑 操 作 , 则 可 能 消 耗 系 统 和 网 络 资 源 , 从 而 导 致 低 劣 的 应 用 程 序 性 能 。<br />

• 有 效 地 使 用 线 程 。 如 果 您 使 用 用 户 界 面 (UI) 线 程 执 行 阻 塞 I/O 绑 定 调 用 , 则 UI 似 乎 不 对 用 户 作 出 响


应 。 因 为 创 建 和 关 闭 线 程 需 要 系 统 开 销 , 所 以 创 建 大 量 不 必 要 的 线 程 可 能 导 致 低 劣 的 性 能 。<br />

• 有 效 地 使 用 事 务 。 如 果 客 户 端 具 有 本 地 数 据 , 则 使 用 原 子 事 务 可 帮 助 您 确 保 该 数 据 是 一 致 的 。 因 为 数 据 是<br />

本 地 的 , 所 以 事 务 也 是 本 地 的 而 不 是 分 布 式 的 。 对 于 脱 机 工 作 的 智 能 客 户 端 而 言 , 对 本 地 数 据 进 行 的 任 何<br />

更 改 都 是 暂 时 的 。 客 户 端 在 重 新 联 机 时 需 要 同 步 更 改 。 对 于 非 本 地 数 据 而 言 , 在 某 些 情 况 下 可 以 使 用 分 布<br />

式 事 务 ( 例 如 , 当 服 务 位 于 具 有 良 好 连 接 性 的 同 一 物 理 位 置 并 且 服 务 支 持 它 时 )。 诸 如 Web 服 务 和 消<br />

息 队 列 之 类 的 服 务 不 支 持 分 布 式 事 务 。<br />

• 优 化 应 用 程 序 启 动 时 间 。 较 短 的 应 用 程 序 启 动 时 间 使 用 户 可 以 更 为 迅 速 地 开 始 与 应 用 程 序 交 互 , 从 而 使 用<br />

户 立 刻 对 应 用 程 序 的 性 能 和 可 用 性 产 生 好 感 。 应 该 对 您 的 应 用 程 序 进 行 适 当 的 设 计 , 以 便 在 应 用 程 序 启 动<br />

时 仅 加 载 那 些 必 需 的 程 序 集 。 因 为 加 载 每 个 程 序 集 都 会 引 起 性 能 开 销 , 所 以 请 避 免 使 用 大 量 程 序 集 。<br />

• 有 效 地 管 理 可 用 资 源 。 低 劣 的 设 计 决 策 ( 例 如 , 实 现 不 必 要 的 完 成 器 , 未 能 在 Dispose 方 法 中 取 消 终<br />

止 , 或 者 未 能 释 放 非 托 管 资 源 ) 可 能 导 致 在 回 收 资 源 时 发 生 不 必 要 的 延 迟 , 并 且 可 能 造 成 使 应 用 程 序 性 能<br />

降 低 的 资 源 泄 漏 。 如 果 应 用 程 序 未 能 正 确 地 释 放 资 源 , 或 者 应 用 程 序 显 式 强 制 进 行 垃 圾 回 收 , 则 可 能 会 妨<br />

碍 CLR 有 效 地 管 理 内 存 。<br />

• 优 化 Windows 窗 体 性 能 。 智 能 客 户 端 应 用 程 序 依 靠 Windows 窗 体 来 提 供 内 容 丰 富 且 响 应 迅 速 的 用<br />

户 界 面 . 您 可 以 使 用 多 种 技 术 来 确 保 Windows 窗 体 提 供 最 佳 性 能 。 这 些 技 术 包 括 降 低 用 户 界 面 的 复 杂<br />

性 , 以 及 避 免 同 时 加 载 大 量 数 据 。<br />

在 许 多 情 况 下 , 从 用 户 角 度 感 受 到 的 应 用 程 序 性 能 起 码 与 应 用 程 序 的 实 际 性 能 同 样 重 要 。 您 可 以<br />

通 过 对 设 计 进 行 某 些 特 定 的 更 改 来 创 建 在 用 户 看 来 性 能 高 得 多 的 应 用 程 序 , 例 如 : 使 用 后 台 异 步<br />

处 理 ( 以 使 UI 能 作 出 响 应 ); 显 示 进 度 栏 以 指 示 任 务 的 进 度 ; 提 供 相 应 的 选 项 以 便 用 户 取 消<br />

长 期 运 行 的 任 务 。<br />

本 节 将 专 门 详 细 讨 论 这 些 问 题 。<br />

数 据 缓 存 原 则<br />

缓 存 是 一 种 能 够 改 善 应 用 程 序 性 能 并 提 供 响 应 迅 速 的 用 户 界 面 的 重 要 技 术 。 您 应 该 考 虑 下 列 选<br />

项 :<br />

• 缓 存 频 繁 检 索 的 数 据 以 减 少 往 返 行 程 。 如 果 您 的 应 用 程 序 必 须 频 繁 地 与 网 络 服 务 交 互 以 检 索 数 据 , 则 应 该<br />

考 虑 在 客 户 端 缓 存 数 据 , 从 而 减 少 通 过 网 络 重 复 获 取 数 据 的 需 要 。 这 可 以 极 大 地 提 高 性 能 , 提 供 对 数 据 的


第<br />

近 乎 即 时 的 访 问 , 并 且 消 除 了 可 能 对 智 能 客 户 端 应 用 程 序 性 能 造 成 不 利 影 响 的 网 络 延 迟 和 中 断 风 险 。<br />

• 缓 存 只 读 引 用 数 据 。 只 读 引 用 数 据 通 常 是 理 想 的 缓 存 对 象 。 此 类 数 据 用 于 提 供 进 行 验 证 和 用 户 界 面 显 示 所<br />

需 的 数 据 , 例 如 , 产 品 说 明 、ID 等 等 。 因 为 客 户 端 无 法 更 改 此 类 数 据 , 所 以 通 常 可 以 在 客 户 端 缓 存 它 而<br />

无 须 进 行 任 何 进 一 步 的 特 殊 处 理 。<br />

• 缓 存 要 发 送 给 位 于 网 络 上 的 服 务 的 数 据 。 您 应 该 考 虑 缓 存 要 发 送 给 位 于 网 络 上 的 服 务 的 数 据 。 例 如 , 如 果<br />

您 的 应 用 程 序 允 许 用 户 输 入 由 在 多 个 窗 体 中 收 集 的 一 些 离 散 数 据 项 组 成 的 定 单 信 息 , 则 请 考 虑 允 许 用 户 输<br />

入 全 部 数 据 , 然 后 在 输 入 过 程 的 结 尾 在 一 个 网 络 调 用 中 发 送 定 单 信 息 。<br />

• 尽 量 少 地 缓 存 高 度 不 稳 定 的 数 据 。 在 缓 存 任 何 不 稳 定 的 数 据 之 前 , 您 需 要 考 虑 在 其 变 得 陈 旧 或 者 由 于 其 他<br />

原 因 变 得 不 可 用 之 前 , 能 够 将 其 缓 存 多 长 时 间 。 如 果 数 据 高 度 不 稳 定 并 且 您 的 应 用 程 序 依 赖 于 最 新 信 息 ,<br />

则 或 许 只 能 将 数 据 缓 存 很 短 一 段 时 间 ( 如 果 可 以 缓 存 )。<br />

• 尽 量 少 地 缓 存 敏 感 数 据 。 您 应 该 避 免 在 客 户 端 上 缓 存 敏 感 数 据 , 因 为 在 大 多 数 情 况 下 , 您 无 法 保 证 客 户 端<br />

的 物 理 安 全 。 但 是 , 如 果 您 必 须 在 客 户 端 上 缓 存 敏 感 数 据 , 则 您 通 常 将 需 要 加 密 数 据 , 该 操 作 本 身 也 会 影<br />

响 性 能 。<br />

有 关 数 据 缓 存 的 其 他 问 题 的 详 细 信 息 , 请 参 阅 本 指 南 的 14H 2 章 。 另 请 参 阅 Improving .NET<br />

Application Performance and Scalability 的 第 3 章 “Design Guidelines for Applicat<br />

ion Performance”(15Hhttp://msdn.microsoft.com/library/default.asp?url=/library/enus/dnpag/html/scalenetchapt03.asp)<br />

的 “Caching” 一 节 以 及 Improving .NET Applic<br />

ation Performance and Scalability 的 第 4 章 “Architecture and Design Review of .<br />

NET Application for Performance and Scalability”(16Hhttp://msdn.microsoft.com/libr<br />

ary/default.asp?url=/library/en-us/dnpag/html/scalenetchapt04.asp)。<br />

网 络 通 讯 原 则<br />

您 将 面 临 的 另 一 个 决 策 是 如 何 设 计 和 使 用 网 络 服 务 , 例 如 ,Web 服 务 。 特 别 地 , 您 应 该 考 虑 与<br />

网 络 服 务 交 互 的 粒 度 、 同 步 性 和 频 率 。 要 获 得 最 佳 的 性 能 和 可 伸 缩 性 , 您 应 该 在 单 个 调 用 中 发 送<br />

更 多 的 数 据 , 而 不 是 在 多 个 调 用 中 发 送 较 少 量 的 数 据 。 例 如 , 如 果 您 的 应 用 程 序 允 许 用 户 在 定 单<br />

中 输 入 多 个 项 , 则 较 好 的 做 法 是 为 所 有 项 收 集 数 据 , 然 后 将 完 成 的 采 购 定 单 一 次 性 发 送 给 服 务 ,<br />

而 不 是 在 多 个 调 用 中 发 送 单 个 项 的 详 细 信 息 。 除 了 降 低 与 进 行 大 量 网 络 调 用 相 关 联 的 系 统 开 销 以<br />

外 , 这 还 可 以 减 少 服 务 和 / 或 客 户 端 内 的 复 杂 状 态 管 理 的 需 要 。


第<br />

第<br />

应 该 将 您 的 智 能 客 户 端 应 用 程 序 设 计 为 尽 可 能 地 使 用 异 步 通 讯 , 因 为 这 将 有 助 于 使 用 户 界 面 快 速<br />

响 应 以 及 并 行 执 行 任 务 。 有 关 如 何 使 用 BeginInvoke 和 EndInvoke 方 法 异 步 启 动 调 用<br />

和 检 索 数 据 的 详 细 信 息 , 请 参 阅 “Asynchronous Programming Overview”(17Hhttp://msdn.<br />

microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpovrasynchro<br />

nousprogrammingoverview.asp)。<br />

注 有 关 设 计 和 构 建 偶 尔 连 接 到 网 络 的 智 能 客 户 端 应 用 程 序 的 详 细 信 息 , 请 参 阅 18H 3 章 “ 建 立 连<br />

接 ” 和 19H 4 章 “ 偶 尔 连 接 的 智 能 客 户 端 ”。<br />

线 程 处 理 原 则<br />

在 应 用 程 序 内 使 用 多 个 线 程 可 能 是 一 种 提 高 其 响 应 性 和 性 能 的 好 方 法 。 特 别 地 , 您 应 该 考 虑 使 用<br />

线 程 来 执 行 可 以 在 后 台 安 全 地 完 成 且 不 需 要 用 户 交 互 的 处 理 。 通 过 在 后 台 执 行 此 类 工 作 , 可 以 使<br />

用 户 能 够 继 续 使 用 应 用 程 序 , 并 且 使 应 用 程 序 的 主 用 户 界 面 线 程 能 够 维 持 应 用 程 序 的 响 应 性 。<br />

适 合 于 在 单 独 的 线 程 上 完 成 的 处 理 包 括 :<br />

• 应 用 程 序 初 始 化 。 请 在 后 台 线 程 上 执 行 漫 长 的 初 始 化 , 以 便 用 户 能 够 尽 快 地 与 您 的 应 用 程 序 交 互 , 尤 其 是<br />

在 应 用 程 序 功 能 的 重 要 或 主 要 部 分 并 不 依 赖 于 该 初 始 化 完 成 时 。<br />

• 远 程 服 务 调 用 。 请 在 单 独 的 后 台 线 程 上 通 过 网 络 进 行 所 有 远 程 调 用 。 很 难 ( 如 果 不 是 无 法 ) 保 证 位 于 网 络<br />

上 的 服 务 的 响 应 时 间 。 在 单 独 的 线 程 上 执 行 这 些 调 用 可 以 减 少 发 生 网 络 中 断 或 延 迟 的 风 险 , 从 而 避 免 对 应<br />

用 程 序 性 能 造 成 不 利 影 响 。<br />

• IO 绑 定 处 理 。 应 该 在 单 独 的 线 程 上 完 成 诸 如 在 磁 盘 上 搜 索 和 排 序 数 据 之 类 的 处 理 。 通 常 , 这 种 工 作 要 受<br />

到 磁 盘 I/O 子 系 统 而 不 是 处 理 器 可 用 性 的 限 制 , 因 此 当 该 工 作 在 后 台 执 行 时 , 您 的 应 用 程 序 可 以 有 效 地<br />

维 持 其 响 应 性 。<br />

尽 管 使 用 多 个 线 程 的 性 能 好 处 可 能 很 显 著 , 但 需 要 注 意 , 线 程 使 用 它 们 自 己 的 资 源 , 并 且 使 用 太<br />

多 的 线 程 可 能 给 处 理 器 ( 它 需 要 管 理 线 程 之 间 的 上 下 文 切 换 ) 造 成 负 担 。 要 避 免 这 一 点 , 请 考 虑<br />

使 用 线 程 池 , 而 不 是 创 建 和 管 理 您 自 己 的 线 程 。 线 程 池 将 为 您 有 效 地 管 理 线 程 , 重 新 使 用 现 有 的<br />

线 程 对 象 , 并 且 尽 可 能 地 减 小 与 线 程 创 建 和 处 置 相 关 联 的 系 统 开 销 。


如 果 用 户 体 验 受 到 后 台 线 程 所 执 行 的 工 作 的 影 响 , 则 您 应 该 总 是 让 用 户 了 解 工 作 的 进 度 。 以 这 种<br />

方 式 提 供 反 馈 可 以 增 强 用 户 对 您 的 应 用 程 序 的 性 能 的 感 觉 , 并 且 防 止 他 或 她 假 设 没 有 任 何 事 情 发<br />

生 。 请 努 力 确 保 用 户 可 以 随 时 取 消 漫 长 的 操 作 。<br />

您 还 应 该 考 虑 使 用 Application 对 象 的 Idle 事 件 来 执 行 简 单 的 操 作 。Idle 事 件 提 供 了 使<br />

用 单 独 的 线 程 来 进 行 后 台 处 理 的 简 单 替 代 方 案 。 当 应 用 程 序 不 再 有 其 他 用 户 界 面 消 息 需 要 处 理 并<br />

且 将 要 进 入 空 闲 状 态 时 , 该 事 件 将 激 发 。 您 可 以 通 过 该 事 件 执 行 简 单 的 操 作 , 并 且 利 用 用 户 不 活<br />

动 的 情 况 。 例 如 :<br />

[C#]<br />

public Form1()<br />

{<br />

InitializeComponent();<br />

Application.Idle += new EventHandler( OnApplicationIdle );<br />

}<br />

private void OnApplicationIdle( object sender, EventArgs e )<br />

{<br />

}<br />

[Visual Basic .NET]<br />

Public Class Form1<br />

Inherits System.Windows.Forms.Form<br />

Public Sub New()<br />

MyBase.New()<br />

InitializeComponent()<br />

AddHandler Application.Idle, AddressOf OnApplicationIdle<br />

End Sub


第<br />

第<br />

Private Sub OnApplicationIdle(ByVal sender As System.Object, ByVal e<br />

As System.EventArgs)<br />

End Sub<br />

End Class<br />

注 有 关 在 智 能 客 户 端 中 使 用 多 个 线 程 的 详 细 信 息 , 请 参 阅 20H 6 章 “ 使 用 多 个 线 程 ”。<br />

事 务 原 则<br />

事 务 可 以 提 供 重 要 的 支 持 , 以 确 保 不 会 违 反 业 务 规 则 并 维 护 数 据 一 致 性 。 事 务 可 以 确 保 一 组 相 关<br />

任 务 作 为 一 个 单 元 成 功 或 失 败 。 您 可 以 使 用 事 务 来 维 护 本 地 数 据 库 和 其 他 资 源 ( 包 括 消 息 队 列 的<br />

队 列 ) 之 间 的 一 致 性 。<br />

对 于 需 要 在 网 络 连 接 不 可 用 时 使 用 脱 机 缓 存 数 据 的 智 能 客 户 端 应 用 程 序 , 您 应 该 将 事 务 性 数 据 排<br />

队 , 并 且 在 网 络 连 接 可 用 时 将 其 与 服 务 器 进 行 同 步 。<br />

您 应 该 避 免 使 用 涉 及 到 位 于 网 络 上 的 资 源 的 分 布 式 事 务 , 因 为 这 些 情 况 可 能 导 致 与 不 断 变 化 的 网<br />

络 和 资 源 响 应 时 间 有 关 的 性 能 问 题 。 如 果 您 的 应 用 程 序 需 要 在 事 务 中 涉 及 到 位 于 网 络 上 的 资 源 ,<br />

则 应 该 考 虑 使 用 补 偿 事 务 , 以 便 使 您 的 应 用 程 序 能 够 在 本 地 事 务 失 败 时 取 消 以 前 的 请 求 。 尽 管 补<br />

偿 事 务 在 某 些 情 况 下 可 能 不 适 用 , 但 它 们 使 您 的 应 用 程 序 能 够 按 照 松 耦 合 方 式 在 事 务 的 上 下 文 内<br />

与 网 络 资 源 交 互 , 从 而 减 少 了 不 在 本 地 计 算 机 控 制 之 下 的 资 源 对 应 用 程 序 的 性 能 造 成 不 利 影 响 的<br />

可 能 性 。<br />

注 有 关 在 智 能 客 户 端 中 使 用 事 务 的 详 细 信 息 , 请 参 阅 21H 3 章 “ 建 立 连 接 ”。<br />

优 化 应 用 程 序 启 动 时 间<br />

快 速 的 应 用 程 序 启 动 时 间 几 乎 可 以 使 用 户 立 即 开 始 与 应 用 程 序 交 互 , 从 而 使 用 户 立 刻 对 应 用 程 序<br />

的 性 能 和 可 用 性 产 生 好 感 。<br />

当 应 用 程 序 启 动 时 , 首 先 加 载 CLR, 再 加 载 应 用 程 序 的 主 程 序 集 , 随 后 加 载 为 解 析 从 应 用 程 序<br />

的 主 窗 体 中 引 用 的 对 象 的 类 型 所 需 要 的 所 有 程 序 集 。CLR 在 该 阶 段 不 会 加 载 所 有 相 关 程 序 集 ;


它 仅 加 载 包 含 主 窗 体 类 上 的 成 员 变 量 的 类 型 定 义 的 程 序 集 。 在 加 载 了 这 些 程 序 集 之 后 , 实 时 (J<br />

IT) 编 译 器 将 在 方 法 运 行 时 编 译 方 法 的 代 码 ( 从 Main 方 法 开 始 )。 同 样 ,JIT 编 译 器 不 会<br />

编 译 您 的 程 序 集 中 的 所 有 代 码 。 相 反 , 将 根 据 需 要 逐 个 方 法 地 编 译 代 码 。<br />

要 尽 可 能 减 少 应 用 程 序 的 启 动 时 间 , 您 应 该 遵 循 下 列 原 则 :<br />

• 尽 可 能 减 少 应 用 程 序 主 窗 体 类 中 的 成 员 变 量 。 这 将 在 CLR 加 载 主 窗 体 类 时 尽 可 能 减 少 必 须 解 析 的 类 型 数<br />

量 。<br />

• 尽 量 不 要 立 即 使 用 大 型 基 类 程 序 集 (XML 库 或 ADO.NET 库 ) 中 的 类 型 。 这 些 程 序 集 的 加 载 很 费 时 间 。<br />

使 用 应 用 程 序 配 置 类 和 跟 踪 开 关 功 能 时 将 引 入 XML 库 。 如 果 要 优 先 考 虑 应 用 程 序 启 动 时 间 , 请 避 免 这 一<br />

点 。<br />

• 尽 可 能 使 用 惰 性 加 载 。 仅 在 需 要 时 获 取 数 据 , 而 不 是 提 前 加 载 和 冻 结 UI。<br />

• 将 应 用 程 序 设 计 为 使 用 较 少 的 程 序 集 。 带 有 大 量 程 序 集 的 应 用 程 序 会 招 致 性 能 开 销 增 加 。 这 些 开 销 来 自 加<br />

载 元 数 据 、 访 问 CLR 中 的 预 编 译 映 像 中 的 各 种 内 存 页 以 加 载 程 序 集 ( 如 果 它 是 用 本 机 映 像 生 成 器 工 具<br />

Ngen.exe 预 编 译 的 )、JIT 编 译 时 间 、 安 全 检 查 等 等 。 您 应 该 考 虑 基 于 程 序 集 的 使 用 模 式 来 合 并 程 序<br />

集 , 以 便 降 低 相 关 联 的 性 能 开 销 。<br />

• 避 免 设 计 将 多 个 组 件 的 功 能 组 合 到 一 个 组 件 中 的 单 一 类 。 将 设 计 分 解 到 多 个 只 须 在 实 际 调 用 时 进 行 编 译 的<br />

较 小 类 。<br />

• 将 应 用 程 序 设 计 为 在 初 始 化 期 间 对 网 络 服 务 进 行 并 行 调 用 。 通 过 在 初 始 化 期 间 调 用 可 以 并 行 运 行 的 网 络<br />

服 务 , 可 以 利 用 服 务 代 理 提 供 的 异 步 功 能 。 这 有 助 于 释 放 当 前 执 行 的 线 程 并 且 并 发 地 调 用 服 务 以 完 成 任 务 。<br />

• 使 用 NGEN.exe 编 译 和 试 验 NGen 和 非 NGen 程 序 集 , 并 且 确 定 哪 个 程 序 集 保 存 了 最 大 数 量 的 工<br />

作 集 页 面 。NGEN.exe( 它 随 附 在 .NET Framework 中 ) 用 于 预 编 译 程 序 集 以 创 建 本 机 映 像 , 该 映 像<br />

随 后 被 存 储 在 全 局 程 序 集 缓 存 的 特 殊 部 分 , 以 便 应 用 程 序 下 次 需 要 它 时 使 用 。 通 过 创 建 程 序 集 的 本 机 映 像 ,<br />

可 以 使 程 序 集 更 快 地 加 载 和 执 行 , 因 为 CLR 不 需 要 动 态 生 成 程 序 集 中 包 含 的 代 码 和 数 据 结 构 。 有 关 详 细<br />

信 息 , 请 参 阅 Improving .NET Application Performance and Scalability 的 第 5 章 “Improvin<br />

g Managed Code Performance” 中 的 “Working Set Considerations” 和 “NGen.exe Explained”<br />

部 分 , 网 址 为 :22Hhttp://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag/ht<br />

ml/scalenetchapt05.asp。


注 如 果 您 使 用 NGEN 预 编 译 程 序 集 , 则 会 立 即 加 载 它 的 所 有 依 赖 程 序 集 。<br />

管 理 可 用 资 源<br />

公 共 语 言 运 行 库 (CLR) 使 用 垃 圾 回 收 器 来 管 理 对 象 生 存 期 和 内 存 使 用 。 这 意 味 着 无 法 再 访 问 的<br />

对 象 将 被 垃 圾 回 收 器 自 动 回 收 , 并 且 自 动 回 收 内 存 。 由 于 多 种 原 因 无 法 再 访 问 对 象 。 例 如 , 可 能<br />

没 有 对 该 对 象 的 任 何 引 用 , 或 者 对 该 对 象 的 所 有 引 用 可 能 来 自 其 他 可 作 为 当 前 回 收 周 期 的 一 部 分<br />

进 行 回 收 的 对 象 。 尽 管 自 动 垃 圾 回 收 使 您 的 代 码 不 必 负 责 管 理 对 象 删 除 , 但 这 意 味 着 您 的 代 码 不<br />

再 对 对 象 的 确 切 删 除 时 间 具 有 显 式 控 制 。<br />

请 考 虑 下 列 原 则 , 以 确 保 您 能 够 有 效 地 管 理 可 用 资 源 :<br />

• 确 保 在 被 调 用 方 对 象 提 供 Dispose 方 法 时 该 方 法 得 到 调 用 。 如 果 您 的 代 码 调 用 了 支 持 Dispose 方 法<br />

的 对 象 , 则 您 应 该 确 保 在 使 用 完 该 对 象 之 后 立 即 调 用 此 方 法 。 调 用 Dispose 方 法 可 以 确 保 抢 先 释 放 非<br />

托 管 资 源 , 而 不 是 等 到 发 生 垃 圾 回 收 。 除 了 提 供 Dispose 方 法 以 外 , 某 些 对 象 还 提 供 其 他 管 理 资 源 的<br />

方 法 , 例 如 ,Close 方 法 。 在 这 些 情 况 下 , 您 应 该 参 考 文 档 资 料 以 了 解 如 何 使 用 其 他 方 法 。 例 如 , 对 于<br />

SqlConnection 对 象 而 言 , 调 用 Close 或 Dispose 都 足 可 以 抢 先 将 数 据 库 连 接 释 放 回 连 接 池 中 。<br />

一 种 可 以 确 保 您 在 对 象 使 用 完 毕 之 后 立 即 调 用 Dispose 的 方 法 是 使 用 Visual C# .NET 中 的 usin<br />

g 语 句 或 Visual Basic .NET 中 的 Try/Finally 块 。<br />

下 面 的 代 码 片 段 演 示 了 Dispose 的 用 法 。<br />

C# 中 的 using 语 句 示 例 :<br />

using( StreamReader myFile = new StreamReader("C:\\ReadMe.Txt")){<br />

string contents = myFile.ReadToEnd();<br />

//... use the contents of the file<br />

} // dispose is called and the StreamReader's resources<br />

released


Visual Basic .NET 中 的 Try/Finally 块 示 例 :<br />

Dim myFile As StreamReader<br />

myFile = New StreamReader("C:\\ReadMe.Txt")<br />

Try<br />

String contents = myFile.ReadToEnd()<br />

'... use the contents of the file<br />

Finally<br />

myFile.Close()<br />

End Try<br />

注 在 C# 和 C++ 中 ,Finalize 方 法 是 作 为 析 构 函 数 实 现 的 。 在 Visual Basic .NET 中 ,Finalize<br />

方 法 是 作 为 Object 基 类 上 的 Finalize 子 例 程 的 重 写 实 现 的 。<br />

• 如 果 您 在 客 户 端 调 用 过 程 中 占 据 非 托 管 资 源 , 则 请 提 供 Finalize 和 Dispose 方 法 。 如 果 您 在 公 共 或<br />

受 保 护 的 方 法 调 用 中 创 建 访 问 非 托 管 资 源 的 对 象 , 则 应 用 程 序 需 要 控 制 非 托 管 资 源 的 生 存 期 。 在 图 8.1<br />

中 , 第 一 种 情 况 是 对 非 托 管 资 源 的 调 用 , 在 此 将 打 开 、 获 取 和 关 闭 资 源 。 在 此 情 况 下 , 您 的 对 象 无 须 提 供<br />

Finalize 和 Dispose 方 法 。 在 第 二 种 情 况 下 , 在 方 法 调 用 过 程 中 占 据 非 托 管 资 源 ; 因 此 , 您 的 对 象<br />

应 该 提 供 Finalize 和 Dispose 方 法 , 以 便 客 户 端 在 使 用 完 该 对 象 后 可 以 立 即 显 式 释 放 资 源 。


图 8.1:Dispose 和 Finalize 方 法 调 用 的 用 法<br />

垃 圾 回 收 通 常 有 利 于 提 高 总 体 性 能 , 因 为 它 将 速 度 的 重 要 性 置 于 内 存 利 用 率 之 上 。 只 有 当 内 存 资<br />

源 不 足 时 , 才 需 要 删 除 对 象 ; 否 则 , 将 使 用 所 有 可 用 的 应 用 程 序 资 源 以 使 您 的 应 用 程 序 受 益 。 但<br />

是 , 如 果 您 的 对 象 保 持 对 非 托 管 资 源 ( 例 如 , 窗 口 句 柄 、 文 件 、GDI 对 象 和 网 络 连 接 ) 的 引 用 ,<br />

则 程 序 员 通 过 在 这 些 资 源 不 再 使 用 时 显 式 释 放 它 们 可 以 获 得 更 好 的 性 能 。 如 果 您 要 在 客 户 端 方 法<br />

调 用 过 程 中 占 据 非 托 管 资 源 , 则 对 象 应 该 允 许 调 用 方 使 用 IDisposable 接 口 ( 它 提 供 Disp<br />

ose 方 法 ) 显 式 管 理 资 源 。 通 过 实 现 IDisposable, 对 象 将 通 知 它 可 被 要 求 明 确 进 行 清 理 ,<br />

而 不 是 等 待 垃 圾 回 收 。 实 现 IDisposable 的 对 象 的 调 用 方 在 使 用 完 该 对 象 后 将 简 单 地 调 用<br />

Dispose 方 法 , 以 便 它 可 以 根 据 需 要 释 放 资 源 。


有 关 如 何 在 某 个 对 象 上 实 现 IDisposable 的 详 细 信 息 , 请 参 阅 Improving .NET Applicat<br />

ion Performance and Scalability 中 的 第 5 章 “Improving Managed Code Performa<br />

nce”, 网 址 为 :23Hhttp://msdn.microsoft.com/library/default.asp?url=/library/en-us/d<br />

npag/html/scalenetchapt05.asp。<br />

注 如 果 您 的 可 处 置 对 象 派 生 自 另 一 个 也 实 现 了 IDisposable 接 口 的 对 象 , 则 您 应 该 调 用 基 类 的<br />

Dispose 方 法 以 使 其 可 以 清 理 它 的 资 源 。 您 还 应 该 调 用 实 现 了 IDisposable 接 口 的 对 象<br />

所 拥 有 的 所 有 对 象 上 的 Dispose。<br />

Finalize 方 法 也 使 您 的 对 象 可 以 在 删 除 时 显 式 释 放 其 引 用 的 任 何 资 源 。 由 于 垃 圾 回 收 器 所 具 有<br />

的 非 确 定 性 , 在 某 些 情 况 下 ,Finalize 方 法 可 能 长 时 间 不 会 被 调 用 。 实 际 上 , 如 果 您 的 应 用 程<br />

序 在 垃 圾 回 收 器 删 除 对 象 之 前 终 止 , 则 该 方 法 可 能 永 远 不 会 被 调 用 。 然 而 , 需 要 使 用 Finalize<br />

方 法 作 为 一 种 后 备 策 略 , 以 防 调 用 方 没 有 显 式 调 用 Dispose 方 法 (Dispose 和 Finalize<br />

方 法 共 享 相 同 的 资 源 清 理 代 码 )。 通 过 这 种 方 式 , 可 能 在 某 个 时 刻 释 放 资 源 , 即 使 这 发 生 在 最<br />

佳 时 刻 之 后 。<br />

注 要 确 保 Dispose 和 Finalize 中 的 清 理 代 码 不 会 被 调 用 两 次 , 您 应 该 调 用 GC.Suppress<br />

Finalize 以 通 知 垃 圾 回 收 器 不 要 调 用 Finalize 方 法 。<br />

垃 圾 回 收 器 实 现 了 Collect 方 法 , 该 方 法 强 制 垃 圾 回 收 器 删 除 所 有 对 象 挂 起 删 除 。 不 应 该 从 应<br />

用 程 序 内 调 用 该 方 法 , 因 为 回 收 周 期 在 高 优 先 级 线 程 上 运 行 。 回 收 周 期 可 能 冻 结 所 有 UI 线 程 ,<br />

从 而 使 得 用 户 界 面 停 止 响 应 。<br />

有 关 详 细 信 息 , 请 参 阅 Improving .NET Application Performance and Scalability 中 的<br />

“Garbage Collection Guidelines”、“Finalize and Dispose Guidelines”、“Dispose Pa<br />

ttern” 和 “Finalize and Dispose Guidelines”, 网 址 为 :24Hhttp://msdn.microsoft.com/libr<br />

ary/default.asp?url=/library/en-us/dnpag/html/scalenetchapt05.asp。<br />

优 化 Windows 窗 体 性 能


Windows 窗 体 为 智 能 客 户 端 应 用 程 序 提 供 了 内 容 丰 富 的 用 户 界 面 , 并 且 您 可 以 使 用 许 多 种 技 术<br />

来 帮 助 确 保 Windows 窗 体 提 供 最 佳 性 能 。 在 讨 论 特 定 技 术 之 前 , 对 一 些 可 以 显 著 提 高 Wind<br />

ows 窗 体 性 能 的 高 级 原 则 进 行 回 顾 是 有 用 的 。<br />

• 小 心 创 建 句 柄 。Windows 窗 体 将 句 柄 创 建 虚 拟 化 ( 即 , 它 动 态 创 建 和 重 新 创 建 窗 口 句 柄 对 象 )。 创 建 句<br />

柄 对 象 的 系 统 开 销 可 能 非 常 大 ; 因 此 , 请 避 免 进 行 不 必 要 的 边 框 样 式 更 改 或 者 更 改 MDI 父 对 象 。<br />

• 避 免 创 建 带 有 太 多 子 控 件 的 应 用 程 序 。Microsoft? Windows? 操 作 系 统 限 制 每 个 进 程 最 多 有 10,000<br />

个 控 件 , 但 您 应 该 避 免 在 窗 体 上 使 用 成 百 上 千 个 控 件 , 因 为 每 个 控 件 都 要 消 耗 内 存 资 源 。<br />

本 节 的 其 余 部 分 讨 论 您 可 以 用 来 优 化 应 用 程 序 用 户 界 面 性 能 的 更 为 具 体 的 技 术 。<br />

使 用 BeginUpdate 和 EndUpdate<br />

许 多 Windows 窗 体 控 件 ( 例 如 ,ListView 和 TreeView 控 件 ) 实 现 了 BeginUpdate<br />

和 EndUpdate 方 法 , 它 们 在 操 纵 基 础 数 据 或 控 件 属 性 时 取 消 了 控 件 的 重 新 绘 制 。 通 过 使 用<br />

BeginUpdate 和 EndUpdate 方 法 , 您 可 以 对 控 件 进 行 重 大 更 改 , 并 且 避 免 在 应 用 这 些<br />

更 改 时 让 控 件 经 常 重 新 绘 制 自 身 。 此 类 重 新 绘 制 会 导 致 性 能 显 著 降 低 , 并 且 用 户 界 面 闪 烁 且 不 反<br />

应 。<br />

例 如 , 如 果 您 的 应 用 程 序 具 有 一 个 要 求 添 加 大 量 节 点 项 的 树 控 件 , 则 您 应 该 调 用 BeginUpda<br />

te, 添 加 所 有 必 需 的 节 点 项 , 然 后 调 用 EndUpdate。 下 面 的 代 码 示 例 显 示 了 一 个 树 控 件 , 该<br />

控 件 用 于 显 示 许 多 个 客 户 的 层 次 结 构 表 示 形 式 及 其 定 单 信 息 。<br />

[C#]<br />

// Suppress repainting the TreeView until all the objects have been c<br />

reated.<br />

TreeView1.BeginUpdate();<br />

// Clear the TreeView.<br />

TreeView1.Nodes.Clear();<br />

// Add a root TreeNode for each Customer object in the ArrayList.


foreach( Customer customer2 in customerArray )<br />

{<br />

TreeView1.Nodes.Add( new TreeNode( customer2.CustomerName ) );<br />

// Add a child TreeNode for each Order object in the current Custome<br />

r.<br />

foreach( Order order1 in customer2.CustomerOrders )<br />

{<br />

TreeView1.Nodes[ customerArray.IndexOf(customer2) ].Nodes.Add(<br />

new TreeNode( customer2.CustomerName + "." + order1.OrderID ) );<br />

}<br />

}<br />

// Begin repainting the TreeView.<br />

TreeView1.EndUpdate();<br />

[Visual Basic .NET]<br />

' Suppress repainting the TreeView until all the objects have<br />

been created.<br />

TreeView1.BeginUpdate()<br />

' Clear the TreeView<br />

TreeView1.Nodes.Clear()<br />

' Add a root TreeNode for each Customer object in the ArrayList<br />

For Each customer2 As Customer In customerArray<br />

TreeView1.Nodes.Add(New TreeNode(customer2.CustomerName))<br />

' Add a child TreeNode for each Order object in the current Customer.<br />

For Each order1 As Order In customer2.CustomerOrders<br />

TreeView1.Nodes(Array.IndexOf(customerArray, customer2)).Nodes.Add( _<br />

New TreeNode(customer2.CustomerName & "." & order1.OrderID))<br />

Next


Next<br />

' Begin repainting the TreeView.<br />

TreeView1.EndUpdate()<br />

即 使 在 您 不 希 望 向 控 件 添 加 许 多 对 象 时 , 您 也 应 该 使 用 BeginUpdate 和 EndUpdate 方<br />

法 。 在 大 多 数 情 况 下 , 您 在 运 行 之 前 将 不 知 道 要 添 加 的 项 的 确 切 个 数 。 因 此 , 为 了 妥 善 处 理 大 量<br />

数 据 以 及 应 付 将 来 的 要 求 , 您 应 该 总 是 调 用 BeginUpdate 和 EndUpdate 方 法 。<br />

注 调 用 Windows 窗 体 控 件 使 用 的 许 多 Collection 类 的 AddRange 方 法 时 , 将 自 动 为 您<br />

调 用 BeginUpdate 和 EndUpdate 方 法 。<br />

使 用 SuspendLayout 和 ResumeLayout<br />

许 多 Windows 窗 体 控 件 ( 例 如 ,ListView 和 TreeView 控 件 ) 都 实 现 了 SuspendLa<br />

yout 和 ResumeLayout 方 法 , 它 们 能 够 防 止 控 件 在 添 加 子 控 件 时 创 建 多 个 布 局 事 件 。<br />

如 果 您 的 控 件 以 编 程 方 式 添 加 和 删 除 子 控 件 或 者 执 行 动 态 布 局 , 则 您 应 该 调 用 SuspendLay<br />

out 和 ResumeLayout 方 法 。 通 过 SuspendLayout 方 法 , 可 以 在 控 件 上 执 行 多 个 操<br />

作 , 而 不 必 为 每 个 更 改 执 行 布 局 。 例 如 , 如 果 您 调 整 控 件 的 大 小 并 移 动 控 件 , 则 每 个 操 作 都 将 引<br />

发 单 独 的 布 局 事 件 。<br />

这 些 方 法 按 照 与 BeginUpdate 和 EndUpdate 方 法 类 似 的 方 式 操 作 , 并 且 在 性 能 和 用 户<br />

界 面 稳 定 性 方 面 提 供 相 同 的 好 处 。<br />

下 面 的 示 例 以 编 程 方 式 向 父 窗 体 中 添 加 按 钮 :<br />

[C#]<br />

private void AddButtons()<br />

{<br />

// Suspend the form layout and add two buttons.<br />

this.SuspendLayout();


Button buttonOK = new Button();<br />

buttonOK.Location = new Point(10, 10);<br />

buttonOK.Size = new Size(75, 25);<br />

buttonOK.Text = "OK";<br />

Button buttonCancel = new Button();<br />

buttonCancel.Location = new Point(90, 10);<br />

buttonCancel.Size = new Size(75, 25);<br />

buttonCancel.Text = "Cancel";<br />

this.Controls.AddRange(new Control[]{buttonOK, buttonCancel});<br />

this.ResumeLayout();<br />

}<br />

[Visual Basic .NET]<br />

Private Sub AddButtons()<br />

' Suspend the form layout and add two buttons<br />

Me.SuspendLayout()<br />

Dim buttonOK As New Button<br />

buttonOK.Location = New Point(10, 10)<br />

buttonOK.Size = New Size(75, 25)<br />

buttonOK.Text = "OK"<br />

Dim buttonCancel As New Button<br />

buttonCancel.Location = New Point(90, 10)<br />

buttonCancel.Size = New Size(75, 25)<br />

buttonCancel.Text = "Cancel"<br />

Me.Controls.AddRange(New Control() { buttonOK, buttonCancel } )<br />

Me.ResumeLayout()<br />

End Sub


每 当 您 添 加 或 删 除 控 件 、 执 行 子 控 件 的 自 动 布 局 或 者 设 置 任 何 影 响 控 件 布 局 的 属 性 ( 例 如 , 大 小 、<br />

位 置 、 定 位 点 或 停 靠 属 性 ) 时 , 您 都 应 该 使 用 SuspendLayout 和 ResumeLayout 方 法 。<br />

处 理 图 像<br />

如 果 您 的 应 用 程 序 显 示 大 量 图 像 文 件 ( 例 如 ,.jpg 和 .gif 文 件 ), 则 您 可 以 通 过 以 位 图 格 式<br />

预 先 呈 现 图 像 来 显 著 改 善 显 示 性 能 。<br />

要 使 用 该 技 术 , 请 首 先 从 文 件 中 加 载 图 像 , 然 后 使 用 PARGB 格 式 将 其 呈 现 为 位 图 。 下 面 的 代<br />

码 示 例 从 磁 盘 中 加 载 文 件 , 然 后 使 用 该 类 将 图 像 呈 现 为 预 乘 的 、Alpha 混 合 RGB 格 式 。 例 如 :<br />

[C#]<br />

if ( image != null && image is Bitmap )<br />

{<br />

Bitmap bm = (Bitmap)image;<br />

Bitmap newImage = new Bitmap( bm.Width, bm.Height,<br />

System.Drawing.Imaging.PixelFormat.Format32bppPArgb );<br />

using ( Graphics g = Graphics.FromImage( newImage ) )<br />

{<br />

g.DrawImage( bm, new Rectangle( 0,0, bm.Width, bm.Height ) );<br />

}<br />

image = newImage;<br />

}<br />

[Visual Basic .NET]<br />

If Not(image Is Nothing) AndAlso (TypeOf image Is Bitmap) Th<br />

en<br />

Dim bm As Bitmap = CType(image, Bitmap)<br />

Dim newImage As New Bitmap(bm.Width, bm.Height, _


System.Drawing.Imaging.PixelFormat.Format32bppPArgb)<br />

Using g As Graphics = Graphics.FromImage(newImage)<br />

g.DrawImage(bm, New Rectangle(0, 0, bm.Width, bm.Height))<br />

End Using<br />

image = newImage<br />

End If<br />

使 用 分 页 和 惰 性 加 载<br />

在 大 多 数 情 况 下 , 您 应 该 仅 在 需 要 时 检 索 或 显 示 数 据 。 如 果 您 的 应 用 程 序 需 要 检 索 和 显 示 大 量 信<br />

息 , 则 您 应 该 考 虑 将 数 据 分 解 到 多 个 页 面 中 , 并 且 一 次 显 示 一 页 数 据 。 这 可 以 使 用 户 界 面 具 有 更<br />

高 的 性 能 , 因 为 它 无 须 显 示 大 量 数 据 。 此 外 , 这 可 以 提 高 应 用 程 序 的 可 用 性 , 因 为 用 户 不 会 同 时<br />

面 对 大 量 数 据 , 并 且 可 以 更 加 容 易 地 导 航 以 查 找 他 或 她 需 要 的 确 切 数 据 。<br />

例 如 , 如 果 您 的 应 用 程 序 显 示 来 自 大 型 产 品 目 录 的 产 品 数 据 , 则 您 可 以 按 照 字 母 顺 序 显 示 这 些 项 ,<br />

并 且 将 所 有 以 “A” 开 头 的 产 品 显 示 在 一 个 页 面 上 , 将 所 有 以 “B” 开 头 的 产 品 显 示 在 下 一 个 页 面 上 。<br />

然 后 , 您 可 以 让 用 户 直 接 导 航 到 适 当 的 页 面 , 以 便 他 或 她 无 须 浏 览 所 有 页 面 就 可 以 获 得 他 或 她 需<br />

要 的 数 据 。<br />

以 这 种 方 式 将 数 据 分 页 还 使 您 可 以 根 据 需 要 获 取 后 台 的 数 据 。 例 如 , 您 可 能 只 需 要 获 取 第 一 页 信<br />

息 以 便 显 示 并 且 让 用 户 与 其 进 行 交 互 。 然 后 , 您 可 以 获 取 后 台 中 的 、 已 经 准 备 好 供 用 户 使 用 的 下<br />

一 页 数 据 。 该 技 术 在 与 数 据 缓 存 技 术 结 合 使 用 时 可 能 特 别 有 效 。<br />

您 还 可 以 通 过 使 用 惰 性 加 载 技 术 来 提 高 智 能 客 户 端 应 用 程 序 的 性 能 。 您 无 须 立 即 加 载 可 能 在 将 来<br />

某 个 时 刻 需 要 的 数 据 或 资 源 , 而 是 可 以 根 据 需 要 加 载 它 们 。 您 可 以 在 构 建 大 型 列 表 或 树 结 构 时 使<br />

用 惰 性 加 载 来 提 高 用 户 界 面 的 性 能 。 在 此 情 况 下 , 您 可 以 在 用 户 需 要 看 到 数 据 时 ( 例 如 , 在 用 户<br />

展 开 树 节 点 时 ) 加 载 它 。<br />

优 化 显 示 速 度


根 据 您 用 于 显 示 用 户 界 面 控 件 和 应 用 程 序 窗 体 的 技 术 , 您 可 以 用 多 种 不 同 的 方 式 来 优 化 应 用 程 序<br />

的 显 示 速 度 。<br />

当 您 的 应 用 程 序 启 动 时 , 您 应 该 考 虑 尽 可 能 地 显 示 简 单 的 用 户 界 面 。 这 将 减 少 启 动 时 间 , 并 且 向<br />

用 户 呈 现 整 洁 且 易 于 使 用 的 用 户 界 面 。 而 且 , 您 应 该 努 力 避 免 引 用 类 以 及 在 启 动 时 加 载 任 何 不 会<br />

立 刻 需 要 的 数 据 。 这 将 减 少 应 用 程 序 和 .NET Framework 初 始 化 时 间 , 并 且 提 高 应 用 程 序 的<br />

显 示 速 度 。<br />

当 您 需 要 显 示 对 话 框 或 窗 体 时 , 您 应 该 在 它 们 做 好 显 示 准 备 之 前 使 其 保 持 隐 藏 状 态 , 以 便 减 少 需<br />

要 的 绘 制 工 作 量 。 这 将 有 助 于 确 保 窗 体 仅 在 初 始 化 之 后 显 示 。<br />

如 果 您 的 应 用 程 序 具 有 的 控 件 含 有 覆 盖 整 个 客 户 端 表 面 区 域 的 子 控 件 , 则 您 应 该 考 虑 将 控 件 背 景<br />

样 式 设 置 为 不 透 明 。 这 可 以 避 免 在 发 生 每 个 绘 制 事 件 时 重 绘 控 件 的 背 景 。 您 可 以 通 过 使 用 Set<br />

Style 方 法 来 设 置 控 件 的 样 式 。 使 用 ControlsStyles.Opaque 枚 举 可 以 指 定 不 透 明 控 件<br />

样 式 。<br />

您 应 该 避 免 任 何 不 必 要 的 控 件 重 新 绘 制 操 作 。 一 种 方 法 是 在 设 置 控 件 的 属 性 时 隐 藏 控 件 。 在 O<br />

nPaint 事 件 中 具 有 复 杂 绘 图 代 码 的 应 用 程 序 能 够 只 重 绘 窗 体 的 无 效 区 域 , 而 不 是 绘 制 整 个 窗<br />

体 。OnPaint 事 件 的 PaintEventArgs 参 数 包 含 一 个 ClipRect 结 构 , 它 指 示 窗 口 的 哪<br />

个 部 分 无 效 。 这 可 以 减 少 用 户 等 待 查 看 完 整 显 示 的 时 间 。<br />

使 用 标 准 的 绘 图 优 化 , 例 如 , 剪 辑 、 双 缓 冲 和 ClipRectangle。 这 还 将 通 过 防 止 对 不 可 见 或<br />

要 求 重 绘 的 显 示 部 分 执 行 不 必 要 的 绘 制 操 作 , 从 而 有 助 于 改 善 智 能 客 户 端 应 用 程 序 的 显 示 性 能 。<br />

有 关 增 强 绘 图 性 能 的 详 细 信 息 , 请 参 阅 Painting techniques using Windows Forms for<br />

the Microsoft .NET Framework, 网 址 为 :25Hhttp://windowsforms.net/articles/window<br />

sformspainting.aspx。<br />

如 果 您 的 显 示 包 含 动 画 或 者 经 常 更 改 某 个 显 示 元 素 , 则 您 应 该 使 用 双 缓 冲 或 多 缓 冲 , 在 绘 制 当 前<br />

图 像 的 过 程 中 准 备 下 一 个 图 像 。System.Windows.Forms 命 名 空 间 中 的 ControlStyle<br />

s 枚 举 适 用 于 许 多 控 件 , 并 且 DoubleBuffer 成 员 可 以 帮 助 防 止 闪 烁 。 启 用 DoubleBuff


26H<br />

er 样 式 将 使 您 的 控 件 绘 制 在 离 屏 缓 冲 中 完 成 , 然 后 同 时 绘 制 到 屏 幕 上 。 尽 管 这 有 助 于 防 止 闪 烁 ,<br />

但 它 的 确 为 分 配 的 缓 冲 区 使 用 了 更 多 内 存 。<br />

27H 返 回 页 首<br />

性 能 调 整 和 诊 断<br />

在 设 计 和 实 现 阶 段 处 理 性 能 问 题 是 实 现 应 用 程 序 性 能 目 标 的 最 划 算 的 方 法 。 但 是 , 您 只 有 在 开 发<br />

阶 段 经 常 且 尽 早 测 试 应 用 程 序 的 性 能 , 才 能 真 正 有 效 地 优 化 应 用 程 序 的 性 能 。<br />

尽 管 针 对 性 能 进 行 设 计 和 测 试 都 很 重 要 , 但 在 这 些 早 期 阶 段 优 化 每 个 组 件 和 所 有 代 码 不 是 有 效 的<br />

资 源 用 法 , 因 此 应 该 予 以 避 免 。 所 以 , 应 用 程 序 可 能 存 在 您 在 设 计 阶 段 未 预 料 到 的 性 能 问 题 。 例<br />

如 , 您 可 能 遇 到 由 于 两 个 系 统 或 组 件 之 间 的 无 法 预 料 的 交 互 而 产 生 的 性 能 问 题 , 或 者 您 可 能 使 用<br />

原 来 存 在 的 、 未 按 希 望 的 方 式 执 行 的 代 码 。 在 此 情 况 下 , 您 需 要 追 究 性 能 问 题 的 根 源 , 以 便 您 可<br />

以 适 当 地 解 决 该 问 题 。<br />

本 节 讨 论 一 些 将 帮 助 您 诊 断 性 能 问 题 以 及 调 整 应 用 程 序 以 获 得 最 佳 性 能 的 工 具 和 技 术 。<br />

制 定 性 能 目 标<br />

当 您 设 计 和 规 划 智 能 客 户 端 应 用 程 序 时 , 您 应 该 仔 细 考 虑 性 能 方 面 的 要 求 , 并 且 定 义 合 适 的 性 能<br />

目 标 。 在 定 义 这 些 目 标 时 , 请 考 虑 您 将 如 何 度 量 应 用 程 序 的 实 际 性 能 。 您 的 性 能 度 量 标 准 应 该 明<br />

确 体 现 应 用 程 序 的 重 要 性 能 特 征 。 请 努 力 避 免 无 法 准 确 度 量 的 模 糊 或 不 完 整 的 目 标 , 例 如 ,“ 应<br />

用 程 序 必 须 快 速 运 行 ” 或 “ 应 用 程 序 必 须 快 速 加 载 ”。 您 需 要 了 解 应 用 程 序 的 性 能 和 可 伸 缩 性 目 标 ,<br />

以 便 您 可 以 设 法 满 足 这 些 目 标 并 且 围 绕 它 们 来 规 划 您 的 测 试 。 请 确 保 您 的 目 标 是 可 度 量 的 和 可 验<br />

证 的 。<br />

定 义 良 好 的 性 能 度 量 标 准 使 您 可 以 准 确 跟 踪 应 用 程 序 的 性 能 , 以 便 您 可 以 确 定 应 用 程 序 是 否 能 够<br />

满 足 它 的 性 能 目 标 。 这 些 度 量 标 准 应 该 包 括 在 应 用 程 序 测 试 计 划 中 , 以 便 可 以 在 应 用 程 序 的 测 试<br />

阶 段 度 量 它 们 。


本 节 重 点 讨 论 与 智 能 客 户 端 应 用 程 序 相 关 的 特 定 性 能 目 标 的 定 义 。 如 果 您 还 要 设 计 和 生 成 客 户 端<br />

应 用 程 序 将 消 耗 的 网 络 服 务 , 则 您 还 需 要 为 这 些 服 务 定 义 适 当 的 性 能 目 标 。 在 此 情 况 下 , 您 应 该<br />

确 保 考 虑 整 个 系 统 的 性 能 要 求 , 以 及 应 用 程 序 各 个 部 分 的 性 能 与 其 他 部 分 以 及 整 个 系 统 之 间 存 在<br />

怎 样 的 关 系 。<br />

考 虑 用 户 的 观 点<br />

当 您 为 智 能 客 户 端 应 用 程 序 确 定 合 适 的 性 能 目 标 时 , 您 应 该 仔 细 考 虑 用 户 的 观 点 。 对 于 智 能 客 户<br />

端 应 用 程 序 而 言 , 性 能 与 可 用 性 和 用 户 感 受 有 关 。 例 如 , 只 要 用 户 能 够 继 续 工 作 并 且 获 得 有 关 操<br />

作 进 度 的 足 够 反 馈 , 用 户 就 可 以 接 受 漫 长 的 操 作 。<br />

在 确 定 要 求 时 , 将 应 用 程 序 的 功 能 分 解 为 多 个 使 用 情 景 或 使 用 案 例 通 常 是 有 用 的 。 您 应 该 识 别 对<br />

于 实 现 特 定 性 能 目 标 而 言 关 键 且 必 需 的 使 用 案 例 和 情 景 。 应 该 将 许 多 使 用 案 例 所 共 有 且 经 常 执 行<br />

的 任 务 设 计 得 具 有 较 高 性 能 。 同 样 , 如 果 任 务 要 求 用 户 全 神 贯 注 并 且 不 允 许 用 户 从 其 切 换 以 执 行<br />

其 他 任 务 , 则 需 要 提 供 优 化 的 且 有 效 的 用 户 体 验 。 如 果 任 务 不 太 经 常 使 用 且 不 会 阻 止 用 户 执 行 其<br />

他 任 务 , 则 可 能 无 须 进 行 大 量 调 整 。<br />

对 于 您 识 别 的 每 个 性 能 敏 感 型 任 务 , 您 都 应 该 精 确 地 定 义 用 户 的 操 作 以 及 应 用 程 序 的 响 应 方 式 。<br />

您 还 应 该 确 定 每 个 任 务 使 用 的 网 络 和 客 户 端 资 源 或 组 件 。 该 信 息 将 影 响 性 能 目 标 , 并 且 将 驱 动 对<br />

性 能 进 行 度 量 的 测 试 。<br />

可 用 性 研 究 提 供 了 非 常 有 价 值 的 信 息 源 , 并 且 可 能 大 大 影 响 性 能 目 标 的 定 义 。 正 式 的 可 用 性 研 究<br />

在 确 定 用 户 如 何 执 行 他 们 的 工 作 、 哪 些 使 用 情 景 是 共 有 的 以 及 哪 些 不 是 共 有 的 、 用 户 经 常 执 行 哪<br />

些 任 务 以 及 从 性 能 观 点 看 来 应 用 程 序 的 哪 些 特 征 是 重 要 的 等 方 面 可 能 非 常 有 用 。 如 果 您 要 生 成 新<br />

的 应 用 程 序 , 您 应 该 考 虑 提 供 应 用 程 序 的 原 型 或 模 型 , 以 便 可 以 执 行 基 本 的 可 用 性 测 试 。<br />

考 虑 应 用 程 序 操 作 环 境<br />

对 应 用 程 序 的 操 作 环 境 进 行 评 估 是 很 重 要 的 , 因 为 这 可 能 对 应 用 程 序 施 加 必 须 在 您 制 定 的 性 能 目<br />

标 中 予 以 反 映 的 约 束 。


位 于 网 络 上 的 服 务 可 能 对 您 的 应 用 程 序 施 加 性 能 约 束 。 例 如 , 您 可 能 需 要 与 您 无 法 控 制 的 Web<br />

服 务 进 行 交 互 。 在 这 种 情 况 下 , 需 要 确 定 该 服 务 的 性 能 , 并 且 确 定 这 是 否 将 对 客 户 端 应 用 程 序<br />

的 性 能 产 生 影 响 。<br />

您 还 应 该 确 定 任 何 相 关 服 务 和 组 件 的 性 能 如 何 随 着 时 间 的 变 化 而 变 化 。 某 些 系 统 会 经 受 相 当 稳 定<br />

的 使 用 , 而 其 他 系 统 则 会 在 一 天 或 一 周 的 特 定 时 间 经 受 变 动 极 大 的 使 用 。 这 些 区 别 可 能 在 关 键 时<br />

间 对 应 用 程 序 的 性 能 造 成 不 利 影 响 。 例 如 , 提 供 应 用 程 序 部 署 和 更 新 服 务 的 服 务 可 能 会 在 星 期 一<br />

早 上 9 点 缓 慢 响 应 , 因 为 所 有 用 户 都 在 此 时 升 级 到 应 用 程 序 的 最 新 版 本 。<br />

另 外 , 还 需 要 准 确 地 对 所 有 相 关 系 统 和 组 件 的 性 能 进 行 建 模 , 以 便 可 以 在 严 格 模 拟 应 用 程 序 的 实<br />

际 部 署 环 境 的 环 境 中 测 试 您 的 应 用 程 序 。 对 于 每 个 系 统 , 您 都 应 该 确 定 性 能 概 况 以 及 最 低 、 平 均<br />

和 最 高 性 能 特 征 。 然 后 , 您 可 以 在 定 义 应 用 程 序 的 性 能 要 求 时 根 据 需 要 使 用 该 数 据 。<br />

您 还 应 该 仔 细 考 虑 用 于 运 行 应 用 程 序 的 硬 件 。 您 将 需 要 确 定 在 处 理 器 、 内 存 、 图 形 功 能 等 方 面 的<br />

目 标 硬 件 配 置 , 或 者 至 少 确 定 一 个 如 果 得 不 到 满 足 则 无 法 保 证 性 能 的 最 低 配 置 。<br />

通 常 , 应 用 程 序 的 业 务 操 作 环 境 将 规 定 一 些 更 为 苛 刻 的 性 能 要 求 。 例 如 , 执 行 实 时 股 票 交 易 的 应<br />

用 程 序 将 需 要 执 行 这 些 交 易 并 及 时 显 示 所 有 相 关 数 据 。<br />

性 能 调 整 过 程<br />

对 应 用 程 序 进 行 性 能 调 整 是 一 个 迭 代 过 程 。 该 过 程 由 一 些 重 复 执 行 直 至 应 用 程 序 满 足 其 性 能 目 标<br />

的 阶 段 组 成 。( 请 参 见 图 8.2。)


图 8.2: 性 能 调 整 过 程<br />

正 如 图 8.2 所 阐 明 的 , 性 能 调 整 要 求 您 完 成 下 列 过 程 :<br />

• 建 立 基 准 。 在 您 开 始 针 对 性 能 调 整 应 用 程 序 时 , 您 必 须 具 有 与 性 能 目 标 、 目 标 和 度 量 标 准 有 关 的 定 义 良 好<br />

的 基 准 。 这 可 能 包 括 应 用 程 序 工 作 集 大 小 、 加 载 数 据 ( 例 如 , 目 录 ) 的 时 间 、 事 务 持 续 时 间 等 等 。<br />

• 收 集 数 据 。 您 将 需 要 通 过 针 对 您 已 经 定 义 的 性 能 目 标 度 量 应 用 程 序 的 性 能 , 来 对 应 用 程 序 性 能 进 行 评 价 。<br />

性 能 目 标 应 该 体 现 特 定 的 且 可 度 量 的 度 量 标 准 , 以 使 您 可 以 在 任 何 时 刻 量 化 应 用 程 序 的 性 能 。 要 使 您 可 以<br />

收 集 性 能 数 据 , 您 可 能 必 须 对 应 用 程 序 进 行 规 范 , 以 便 可 以 发 布 和 收 集 必 需 的 性 能 数 据 。 下 一 节 将 详 细 讨<br />

论 您 可 以 用 来 完 成 这 一 工 作 的 一 些 选 项 。<br />

• 分 析 结 果 。 在 收 集 应 用 程 序 的 性 能 数 据 之 后 , 您 将 能 够 通 过 确 定 哪 些 应 用 程 序 功 能 要 求 最 多 的 关 注 , 来 区<br />

分 性 能 调 整 工 作 的 轻 重 缓 急 。 此 外 , 您 可 以 使 用 该 数 据 来 确 定 任 何 性 能 瓶 颈 的 位 置 。 通 常 , 您 将 只 能 够 通<br />

过 收 集 更 详 细 的 性 能 数 据 来 确 定 瓶 颈 的 确 切 位 置 : 例 如 , 通 过 使 用 应 用 程 序 规 范 。 性 能 分 析 工 具 可 能 帮 助<br />

您 识 别 瓶 颈 。<br />

• 调 整 应 用 程 序 。 在 已 经 识 别 瓶 颈 之 后 , 您 可 能 需 要 修 改 应 用 程 序 或 其 配 置 , 以 便 尝 试 解 决 问 题 。 您 应 该 致<br />

力 于 将 更 改 降 低 至 最 低 限 度 , 以 便 可 以 确 定 更 改 对 应 用 程 序 性 能 的 影 响 。 如 果 您 同 时 进 行 多 项 更 改 , 可 能<br />

难 以 确 定 每 项 更 改 对 应 用 程 序 的 总 体 性 能 的 影 响 。<br />

• 测 试 和 度 量 。 在 更 改 应 用 程 序 或 其 配 置 之 后 , 您 应 该 再 次 测 试 它 以 确 定 更 改 具 有 的 效 果 , 并 且 使 新 的 性 能<br />

数 据 得 以 收 集 。 性 能 工 作 通 常 要 求 进 行 体 系 结 构 或 其 他 具 有 较 高 影 响 的 更 改 , 因 此 彻 底 的 测 试 是 很 关 键 的 。


您 的 应 用 程 序 测 试 计 划 应 该 针 对 预 料 到 的 所 有 情 况 , 在 配 置 了 适 当 硬 件 和 软 件 的 客 户 计 算 机 上 演 习 应 用 程<br />

序 所 实 现 的 完 整 范 围 的 功 能 。 如 果 您 的 应 用 程 序 使 用 网 络 资 源 , 则 应 该 加 载 这 些 资 源 , 以 便 您 可 以 获 得 有<br />

关 应 用 程 序 在 此 类 环 境 中 所 具 有 的 性 能 的 准 确 度 量 。<br />

上 述 过 程 将 使 您 可 以 通 过 针 对 特 定 目 标 度 量 应 用 程 序 的 总 体 性 能 , 来 重 点 解 决 特 定 的 性 能 问 题 。<br />

性 能 工 具<br />

您 可 以 使 用 许 多 工 具 来 帮 助 您 收 集 和 分 析 应 用 程 序 的 性 能 数 据 。 本 节 中 介 绍 的 每 种 工 具 都 具 有 不<br />

同 的 功 能 , 您 可 以 使 用 这 些 功 能 来 度 量 、 分 析 和 查 找 应 用 程 序 中 的 性 能 瓶 颈 。<br />

注 除 了 这 里 介 绍 的 工 具 以 外 , 您 还 可 以 使 用 其 他 一 些 选 项 和 第 三 方 工 具 。 有 关 其 他 日 志 记 录 和 异<br />

常 管 理 选 项 的 说 明 , 请 参 阅 Exception Management Architecture Guide, 网 址 为 :<br />

28Hhttp://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/exc<br />

eptdotnet.asp<br />

在 决 定 哪 些 工 具 最 适 合 您 的 需 要 之 前 , 您 应 该 仔 细 考 虑 您 的 确 切 要 求 。<br />

使 用 性 能 日 志 和 警 报<br />

性 能 日 志 和 警 报 是 作 为 Windows 操 作 系 统 的 一 部 分 发 行 的 一 种 管 理 性 能 监 控 工 具 。 它 依 靠 由<br />

各 种 Windows 组 件 、 子 系 统 和 应 用 程 序 发 布 的 性 能 计 数 器 , 使 您 可 以 跟 踪 资 源 使 用 情 况 以 及<br />

针 对 时 间 以 图 形 方 式 绘 制 它 们 。<br />

您 可 以 使 用 Performance Logs and Alerts 来 监 控 标 准 的 性 能 计 数 器 ( 例 如 , 内 存 使 用 情 况<br />

或 处 理 器 使 用 情 况 ), 或 者 您 可 以 定 义 您 自 己 的 自 定 义 计 数 器 来 监 控 应 用 程 序 特 定 的 活 动 。<br />

.NET CLR 提 供 了 许 多 有 用 的 性 能 计 数 器 , 它 们 使 您 可 以 洞 察 应 用 程 序 性 能 的 好 坏 。 关 系 比 较<br />

大 的 一 些 性 能 对 象 是 :<br />

• .NET CLR 内 存 。 提 供 有 关 托 管 .NET 应 用 程 序 内 存 使 用 情 况 的 数 据 , 包 括 应 用 程 序 正 在 使 用 的 内 存 数<br />

量 以 及 对 未 使 用 的 对 象 进 行 垃 圾 回 收 所 花 费 的 时 间 。


•.NET CLR 加 载 。 提 供 有 关 应 用 程 序 正 在 使 用 的 类 和 应 用 程 序 域 的 数 量 的 数 据 , 并 且 提 供 有 关 它 们 的 加<br />

载 和 卸 载 速 率 的 数 据 。<br />

•.NET CLR 锁 和 线 程 。 提 供 与 应 用 程 序 内 使 用 的 线 程 有 关 的 性 能 数 据 , 包 括 线 程 个 数 以 及 试 图 同 时 对 受<br />

保 护 的 资 源 进 行 访 问 的 线 程 之 间 的 争 用 率 。<br />

•.NET CLR 网 络 。 提 供 与 通 过 网 络 发 送 和 接 收 数 据 有 关 的 性 能 计 数 器 , 包 括 每 秒 发 送 和 接 收 的 字 节 数 以<br />

及 活 动 连 接 的 个 数 。<br />

•.NET CLR 异 常 。 提 供 有 关 应 用 程 序 所 引 发 和 捕 获 的 异 常 个 数 的 报 告 。<br />

有 关 上 述 计 数 器 、 它 们 的 阈 值 、 要 度 量 的 内 容 以 及 如 何 度 量 它 们 的 详 细 信 息 , 请 参 阅 Improvi<br />

ng .NET Application Performance and Scalability 的 第 15 章 “Measuring .NET Ap<br />

plication Performance” 中 的 “CLR and Managed Code” 部 分 , 网 址 为 :29Hhttp://msdn.mi<br />

crosoft.com/library/default.asp?url=/library/en-us/dnpag/html/scalenetchapt15.a<br />

sp。<br />

您 的 应 用 程 序 还 可 以 提 供 您 可 以 通 过 使 用 性 能 日 志 和 警 报 轻 松 监 控 的 、 应 用 程 序 特 定 的 性 能 计 数<br />

器 。 您 可 以 像 以 下 示 例 所 显 示 的 那 样 , 定 义 自 定 义 性 能 计 数 器 :<br />

[C#]<br />

PerformanceCounter counter = new PerformanceCounter( "Category",<br />

"CounterName", false );<br />

[Visual Basic .NET]<br />

Dim counter As New PerformanceCounter("Category", "CounterName", Fals<br />

e)<br />

在 创 建 性 能 计 数 器 对 象 之 后 , 您 可 以 为 您 的 自 定 义 性 能 计 数 器 指 定 类 别 , 并 将 所 有 相 关 计 数 器 保<br />

存 在 一 起 。PerformanceCounter 类 在 System.Diagnostics 命 名 空 间 中 定 义 , 该 命<br />

名 空 间 中 还 定 义 了 其 他 一 些 可 用 于 读 取 和 定 义 性 能 计 数 器 和 类 别 的 类 。 有 关 创 建 自 定 义 性 能 计 数<br />

器 的 详 细 信 息 , 请 参 阅 知 识 库 中 编 号 为 317679 的 文 章 “How to create and make chang


es to a custom counter for the Windows Performance Monitor by using Visual<br />

Basic .NET”, 网 址 为 :30Hhttp://support.microsoft.com/default.aspx?scid=kb;en-us;31<br />

7679。<br />

注 要 注 册 性 能 计 数 器 , 您 必 须 首 先 注 册 该 类 别 。 您 必 须 具 有 足 够 的 权 限 才 能 注 册 性 能 计 数 器 类 别<br />

( 它 可 能 影 响 您 部 署 应 用 程 序 的 方 式 )。<br />

规 范<br />

您 可 以 使 用 许 多 工 具 和 技 术 来 帮 助 您 对 应 用 程 序 进 行 规 范 , 并 且 生 成 度 量 应 用 程 序 性 能 所 需 的 信<br />

息 。 这 些 工 具 和 技 术 包 括 :<br />

• Event Tracing for Windows (ETW)。 该 ETW 子 系 统 提 供 了 一 种 系 统 开 销 较 低 ( 与 性 能 日 志 和<br />

警 报 相 比 ) 的 手 段 , 用 以 监 控 具 有 负 载 的 系 统 的 性 能 。 这 主 要 用 于 必 须 频 繁 记 录 事 件 、 错 误 、 警 告 或 审 核<br />

的 服 务 器 应 用 程 序 。 有 关 详 细 信 息 , 请 参 阅 Microsoft Platform SDK 中 的 “Event Tracing”, 网 址 为 :<br />

31Hhttp://msdn.microsoft.com/library/default.asp?url=/library/en-us/perfmon/base/event_tr<br />

acing.asp。<br />

• Enterprise Instrumentation Framework (EIF)。EIF 是 一 种 可 扩 展 且 可 配 置 的 框 架 , 您 可 以<br />

使 用 它 来 对 智 能 客 户 端 应 用 程 序 进 行 规 划 。 它 提 供 了 一 种 可 扩 展 的 事 件 架 构 和 统 一 的 API — 它 使 用 W<br />

indows 中 内 置 的 现 有 事 件 、 日 志 记 录 和 跟 踪 机 制 , 包 括 Windows Management Instrumentation<br />

(WMI)、Windows Event Log 和 Windows Event Tracing。 它 大 大 简 化 了 发 布 应 用 程 序 事 件 所 需<br />

的 编 码 。 如 果 您 计 划 使 用 EIF, 则 需 要 通 过 使 用 EIF .msi 在 客 户 计 算 机 上 安 装 EIF。 如 果 您 要 在 智 能<br />

客 户 端 应 用 程 序 中 使 用 EIF, 则 需 要 在 决 定 应 用 程 序 的 部 署 方 式 时 考 虑 这 一 要 求 。 有 关 详 细 信 息 , 请 参<br />

阅 “How To:Use EIF”, 网 址 为 :32Hhttp://msdn.microsoft.com/library/default.asp?url=/library/<br />

en-us/dnpag/html/scalenethowto14.asp。<br />

• Logging Application Block。Logging Application Block 提 供 了 可 扩 展 且 可 重 用 的 代 码 组 件 ,<br />

以 帮 助 您 生 成 规 范 化 的 应 用 程 序 。 它 建 立 在 EIF 的 功 能 基 础 之 上 , 以 提 供 某 些 功 能 , 例 如 , 针 对 事 件 架<br />

构 的 增 强 功 能 、 多 个 日 志 级 别 、 附 加 的 事 件 接 收 等 等 。 有 关 详 细 信 息 , 请 参 阅 “Logging Application Bl<br />

ock”, 网 址 为 “33Hhttp://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag/htm<br />

l/Logging.asp。


• Windows Management Instrumentation (WMI)。WMI 组 件 是 Windows 操 作 系 统 的 一 部<br />

分 , 并 且 提 供 了 用 于 访 问 企 业 中 的 管 理 信 息 和 控 件 的 编 程 接 口 。 系 统 管 理 员 常 用 它 来 自 动 完 成 管 理 任 务 ( 通<br />

过 使 用 调 用 WMI 组 件 的 脚 本 )。 有 关 详 细 信 息 , 请 参 阅 Windows Management Information, 网<br />

址 为 :34Hhttp://msdn.microsoft.com/library/default.asp?url=/library/en-us/wmisdk/wmi/wmi<br />

_start_page.asp。<br />

• 调 试 和 跟 踪 类 。.NET Framework 在 System.Diagnosis 下 提 供 了 Debug 和 Trace 类 来 对 代<br />

码 进 行 规 范 。Debug 类 主 要 用 于 打 印 调 试 信 息 以 及 检 查 是 否 有 断 言 。Trace 类 使 您 可 以 对 发 布 版 本 进<br />

行 规 范 , 以 便 在 运 行 时 监 控 应 用 程 序 的 完 好 状 况 。 在 Visual Studio .NET 中 , 默 认 情 况 下 启 用 跟 踪 。<br />

在 使 用 命 令 行 版 本 时 , 您 必 须 为 编 译 器 添 加 /d:Trace 标 志 , 或 者 在 Visual C# .NET 源 代 码 中 添 加<br />

#define TRACE, 以 便 启 用 跟 踪 。 对 于 Visual Basic .NET 源 代 码 , 您 必 须 为 命 令 行 编 译 器 添 加<br />

/d:TRACE=True。 有 关 详 细 信 息 , 请 参 阅 知 识 库 中 编 号 为 815788 的 文 章 “HOW TO:Trace and<br />

Debug in Visual C# .NET”, 网 址 为 :35Hhttp://support.microsoft.com/default.aspx?scid=kb;e<br />

n-us;815788。<br />

CLR Profiler<br />

CLR Profiler 是 Microsoft 提 供 的 一 种 内 存 分 析 工 具 , 并 且 可 以 从 MSDN 下 载 。 它 使 您 能<br />

够 查 看 应 用 程 序 进 程 的 托 管 堆 以 及 调 查 垃 圾 回 收 器 的 行 为 。 使 用 该 工 具 , 您 可 以 获 取 有 关 应 用 程<br />

序 的 执 行 、 内 存 分 配 和 内 存 消 耗 的 有 用 信 息 。 这 些 信 息 可 以 帮 助 您 了 解 应 用 程 序 的 内 存 使 用 方 式<br />

以 及 如 何 优 化 应 用 程 序 的 内 存 使 用 情 况 。<br />

CLR Profiler 可 从 36Hhttp://msdn.microsoft.com/netframework/downloads/tools/defa<br />

ult.aspx 获 得 。 有 关 如 何 使 用 CLR Profiler 工 具 的 详 细 信 息 , 另 请 参 阅 “How to use CLR<br />

Profiler”, 网 址 为 :37Hhttp://msdn.microsoft.com/library/default.asp?url=/library/en-u<br />

s/dnpag/html/scalenethowto13.asp?frame=true。<br />

CLR Profiler 在 日 志 文 件 中 记 录 内 存 消 耗 和 垃 圾 回 收 器 行 为 信 息 。 然 后 , 您 可 以 使 用 一 些 不 同<br />

的 图 形 视 图 , 通 过 CLR Profiler 来 分 析 该 数 据 。 一 些 比 较 重 要 的 视 图 是 :<br />

• Allocation Graph。 显 示 有 关 对 象 分 配 方 式 的 调 用 堆 栈 。 您 可 以 使 用 该 视 图 来 查 看 方 法 进 行 的 每 个 分<br />

配 的 系 统 开 销 , 隔 离 您 不 希 望 发 生 的 分 配 , 以 及 查 看 方 法 可 能 进 行 的 过 度 分 配 。


39H<br />

41H<br />

• Assembly, Module, Function, and Class Graph。 显 示 哪 些 方 法 造 成 了 哪 些 程 序 集 、 函 数 、<br />

模 块 或 类 的 加 载 。<br />

• Call Graph。 使 您 可 以 查 看 哪 些 方 法 调 用 了 其 他 哪 些 方 法 以 及 相 应 的 调 用 频 率 。 您 可 以 使 用 该 图 表 来 确<br />

定 库 调 用 的 系 统 开 销 , 以 及 调 用 了 哪 些 方 法 或 对 特 定 方 法 进 行 了 多 少 个 调 用 。<br />

• Time Line。 提 供 了 有 关 应 用 程 序 执 行 的 基 于 文 本 的 、 按 时 间 顺 序 的 层 次 结 构 视 图 。 使 用 该 视 图 可 以 查<br />

看 分 配 了 哪 些 类 型 以 及 这 些 类 型 的 大 小 。 您 还 可 以 使 用 该 视 图 查 看 方 法 调 用 使 得 哪 些 程 序 集 被 加 载 , 并 且<br />

分 析 您 不 希 望 发 生 的 分 配 。 您 可 以 分 析 完 成 器 的 使 用 情 况 , 并 且 识 别 尚 未 实 现 或 调 用 Close 或 Dispo<br />

se 从 而 导 致 瓶 颈 的 方 法 。<br />

您 可 以 使 用 CLR Profiler.exe 来 识 别 和 隔 离 与 垃 圾 回 收 有 关 的 问 题 。 这 包 括 内 存 消 耗 问 题 ( 例<br />

如 , 过 度 或 未 知 的 分 配 、 内 存 泄 漏 、 生 存 期 很 长 的 对 象 ) 以 及 在 执 行 垃 圾 回 收 时 花 费 的 时 间 的 百<br />

分 比 。<br />

注 有 关 如 何 使 用 CLR Profiler 工 具 的 详 细 信 息 , 请 参 阅 “Improving .NET Application Per<br />

formance and Scalability”, 网 址 为 :38Hhttp://msdn.microsoft.com/library/default.asp?<br />

url=/library/en-us/dnpag/html/scalenethowto13.asp?frame=true。<br />

40H 返 回 页 首<br />

小 结<br />

要 完 全 实 现 智 能 客 户 端 应 用 程 序 的 潜 能 , 您 需 要 在 应 用 程 序 的 设 计 阶 段 仔 细 考 虑 性 能 问 题 。 通 过<br />

在 早 期 阶 段 解 决 这 些 性 能 问 题 , 您 可 以 在 应 用 程 序 设 计 过 程 中 控 制 成 本 , 并 减 小 在 开 发 周 期 的 后<br />

期 陷 入 性 能 问 题 的 可 能 性 。<br />

本 章 分 析 了 许 多 不 同 的 技 术 , 您 可 以 在 规 划 和 设 计 智 能 客 户 端 应 用 程 序 时 使 用 这 些 技 术 , 以 确 保<br />

优 化 它 们 的 性 能 。 本 章 还 考 察 了 您 可 以 用 来 确 定 智 能 客 户 端 应 用 程 序 内 的 性 能 问 题 的 一 些 工 具 和<br />

技 术 。<br />

42H 返 回 页 首


参 考 资 料<br />

有 关 详 细 信 息 , 请 参 阅 以 下 内 容 :<br />

• 43Hhttp://msdn.microsoft.com/perf<br />

• 44Hhttp://www.windowsforms.net/Default.aspx<br />

• 45Hhttp://msdn.microsoft.com/vstudio/using/understand/perf/<br />

• 46Hhttp://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetcomp/html/netcfi<br />

mproveformloadperf.asp<br />

• 47Hhttp://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndotnet/html/highperf<br />

managedapps.asp<br />

• 48Hhttp://msdn.microsoft.com/msdnmag/issues/02/08/AdvancedBasics/default.aspx<br />

• 49Hhttp://msdn.microsoft.com/library/default.asp?url=/msdnmag/issues/04/01/NET/toc.asp?<br />

frame=true<br />

• 50Hhttp://msdn.microsoft.com/library/default.asp?url=/msdnmag/issues/03/02/Multithreadin<br />

g/toc.asp?frame=true<br />

posted on 2007-12-10 17:04 51H<br />

寒 蝉 阅 读 (288) 52H<br />

评 论 (0) 53H<br />

编 辑 54H<br />

收 藏 所 属 分 类 : 55HSmart<br />

Client( 智 能 客 户 端 ) 系 列 学 习 笔 记


今 天 看 MSDN 里 头 的 C# 委 托 示 例 , 代 码 中 的 注 释 是 英 文 的 , 看 着 不 舒 服 , 我 把 它 翻 译 了 一 下 ,<br />

示 例 中 提 到 的 书 也 换 成 了 中 文 的 ,<br />

using System;<br />

// 书 店<br />

namespace Bookstore<br />

{<br />

using System.Collections;<br />

// 描 述 图 书 列 表 中 的 书<br />

public struct Book<br />

{<br />

public string Title;<br />

public string Author;<br />

public decimal Price;<br />

public bool Paperback;<br />

// 书 名<br />

// 作 者<br />

// 价 格<br />

// 是 不 是 平 装<br />

}<br />

public Book(string title, string author, decimal price, bool paperBack)<br />

{<br />

Title = title;<br />

Author = author;<br />

Price = price;<br />

Paperback = paperBack;<br />

}<br />

// 声 明 一 个 委 托 类 型 , 用 来 处 理 图 书 :<br />

public delegate void ProcessBookDelegate(Book book);<br />

// 维 护 一 个 图 书 数 据 库<br />

public class BookDB<br />

{<br />

// 数 据 库 中 所 有 图 书 的 列 表<br />

ArrayList list = new ArrayList();<br />

// 向 数 据 库 添 加 一 本 书<br />

public void AddBook(string title, string author, decimal price, bool paperBack)<br />

{<br />

list.Add(new Book(title, author, price, paperBack));<br />

}


}<br />

}<br />

// 调 用 传 递 进 来 的 委 托 , 处 理 每 一 本 书<br />

public void ProcessPaperbackBooks(ProcessBookDelegate processBook)<br />

{<br />

foreach (Book b in list)<br />

{<br />

if (b.Paperback)<br />

// 调 用 委 托 :<br />

processBook(b);<br />

}<br />

}<br />

// 使 用 Bookstore 中 的 类 :<br />

namespace BookTestClient<br />

{<br />

using Bookstore;<br />

// 计 算 图 书 总 价 和 平 均 价 格<br />

class PriceTotaller<br />

{<br />

int countBooks = 0;// 图 书 本 数<br />

decimal priceBooks = 0.0m;// 图 书 总 价<br />

// 添 加 图 书<br />

internal void AddBookToTotal(Book book)<br />

{<br />

countBooks += 1;<br />

priceBooks += book.Price;<br />

}<br />

}<br />

// 平 均 价 格<br />

internal decimal AveragePrice()<br />

{<br />

return priceBooks / countBooks;<br />

}<br />

// 这 个 类 用 来 测 试 图 书 数 据 库<br />

class Test


{<br />

// 打 印 书 名<br />

static void PrintTitle(Book b)<br />

{<br />

Console.WriteLine(" {0}", b.Title);<br />

}<br />

// 主 函 数 , 程 序 从 这 里 开 始 执 行<br />

static void Main()<br />

{<br />

BookDB bookDB = new BookDB();<br />

// 用 几 本 书 初 始 化 数 据 库<br />

AddBooks(bookDB);<br />

// 打 印 所 有 平 装 书 的 书 名<br />

Console.WriteLine(" 平 装 书 :");<br />

// 创 建 一 个 与 静 态 方 法 Test.PrintTitle 相 关 联 的 委 托 :<br />

bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(PrintTitle));<br />

// 用 一 个 PriceTotaller 对 象 获 取 平 装 书 的 平 均 价 格<br />

PriceTotaller totaller = new PriceTotaller();<br />

// 创 建 一 个 与 对 象 totaller 的 实 例 方 法 AddBookToTotal 相 关 联 的 委 托 实 例<br />

bookDB.ProcessPaperbackBooks(new<br />

ProcessBookDelegate(totaller.AddBookToTotal));<br />

Console.WriteLine(" 平 装 书 的 平 均 价 格 是 : ${0:#.##}",<br />

totaller.AveragePrice());<br />

}<br />

}<br />

}<br />

// 用 几 本 书 初 始 化 数 据 库<br />

static void AddBooks(BookDB bookDB)<br />

{<br />

bookDB.AddBook(" 呐 喊 ", " 鲁 迅 ", 19.95m,true);<br />

bookDB.AddBook(" 我 的 人 生 哲 学 "," 王 蒙 ", 39.95m,true);<br />

bookDB.AddBook(" 三 重 门 ", " 韩 寒 ", 129.95m,false);<br />

bookDB.AddBook(" 那 小 子 真 帅 ", " 可 爱 淘 ", 12.00m,true);<br />

}


C# 异 步 调 用 机 制 的 理 解 ( 一 ) - 思 索 的 秋 天 - 博 客 园<br />

通 常 , 在 进 行 一 个 对 象 的 方 法 调 用 时 , 对 象 执 行 期 间 客 户 端 通 常 都 是 堵 塞 的 , 只 有 等 方 法 执 行 完<br />

毕 返 回 控 制 权 时 才 会 回 到 客 户 端 , 然 而 某 些 时 候 , 我 们 需 要 异 步 调 用 方 法 , 即 对 象 在 后 台 执 行 方<br />

法 调 用 , 控 制 权 可 以 立 即 返 回 到 客 户 端 , 随 后 能 以 某 种 方 式 通 知 客 户 端 已 经 执 行 完 毕 , 这 种 执 行<br />

模 式 就 是 所 谓 的 异 步 调 用 方 法 , 而 操 作 就 是 大 家 所 知 的 异 步 调 用 。 异 步 操 作 通 常 用 于 执 行 完 成 时<br />

间 可 能 较 长 的 任 务 , 如 打 开 大 文 件 、 连 接 远 程 计 算 机 或 查 询 数 据 库 。 异 步 操 作 在 主 应 用 程 序 线 程<br />

以 外 的 线 程 中 执 行 。 应 用 程 序 调 用 方 法 异 步 执 行 某 个 操 作 时 , 应 用 程 序 可 在 异 步 方 法 执 行 其 任 务<br />

时 继 续 执 行 。<br />

.NET Framework 为 异 步 操 作 提 供 两 种 设 计 模 式 :<br />

使 用 IAsyncResult 对 象 的 异 步 操 作 。<br />

使 用 事 件 的 异 步 操 作<br />

今 天 主 要 讲 的 是 使 用 IAsyncResult 对 象 的 异 步 操 作 , 利 用 一 个 委 托 来 进 行 异 步 编 程 。<br />

一 : 异 步 调 用 编 程 模 型<br />

支 持 异 步 调 用 使 用 多 线 程 肯 定 是 必 须 的 , 但 是 如 果 每 一 次 异 步 调 用 都 创 建 一 个 新 线 程 的 话 肯 定 是<br />

一 个 资 源 的 浪 费 。 在 .Net 中 , 为 我 们 提 供 了 线 程 池 , 线 程 池 在 我 们 每 次 进 行 异 步 调 用 的 时 候 都<br />

会 给 调 用 方 分 配 一 个 线 程 , 然 后 控 制 权 会 在 很 短 的 时 间 内 返 回 到 客 户 端 。<br />

总 的 来 说 BeginInvoke() 方 法 发 起 一 个 异 步 调 用 ,EndInvoke() 管 理 方 法 的 完 成 , 包 括 获 取 输<br />

出 参 数 和 返 回 值 。 要 进 行 异 步 调 用 的 说 明 还 需 要 理 解 很 多 其 他 的 概 念 , 包 括 委 托 , 事 件 , 多 线 程<br />

等 , 这 些 概 念 我 就 不 一 一 道 来 了 。<br />

下 面 是 整 个 说 明 过 程 中 用 到 的 一 个 类 ( 摘 抄 而 来 ):<br />

public class CalCulator<br />

{<br />

public int Add(int argument1, int argument2)<br />

{<br />

return argument1 + argument2;<br />

}<br />

public int Subtract(int argument1, int argument2)<br />

{<br />

return argument1 - argument2;<br />

}<br />

}<br />

一 个 委 托 : public delegate int BinaryOperation(int argument1, int argument2);<br />

二 : 使 用 beginInvoke() 和 EndInvoke() 方 法<br />

异 步 委 托 提 供 以 异 步 方 式 调 用 同 步 方 法 的 能 力 。 当 同 步 调 用 一 个 委 托 时 ,“Invoke” 方 法 直 接<br />

对 当 前 线 程 调 用 目 标 方 法 。 如 果 编 译 器 支 持 异 步 委 托 , 则 它 将 生 成 “Invoke” 方 法 以 及<br />

“BeginInvoke” 和 “EndInvoke” 方 法 。 如 果 调 用 “BeginInvoke” 方 法 , 则 公 共 语 言 运 行<br />


(CLR)<br />

将 对 请 求 进 行 排 队 并 立 即 返 回 到 调 用 方 。 将 对 来 自 线 程 池 的 线 程 调 用 该 目 标 方 法 。 提 交 请 求 的 原<br />

始 线 程 自 由 地 继 续 与 目 标 方 法 并 行 执 行 , 该 目 标 方 法 是 对 线 程 池 线 程 运 行 的 。 如 果 在 对<br />

“BeginInvoke” 方 法 的 调 用 中 指 定 了 回 调 方 法 , 则 当 目 标 方 法 返 回 时 将 调 用 该 回 调 方 法 。 在<br />

回 调 方 法 中 ,“EndInvoke” 方 法 获 取 返 回 值 和 所 有 输 入 / 输 出 参 数 。 如 果 在 调 用 “BeginInvoke”<br />

时 未 指 定 任 何 回 调 方 法 , 则 可 以 从 调 用 “BeginInvoke” 的 线 程 中 调 用 “EndInvoke”。<br />

对 于 上 面 的 文 字 意 思 用 代 码 表 示 如 下 :<br />

1. 编 译 器 生 成 的 BinaryOperaion 定 义 :<br />

public sealed class BinaryOperaion : MulticastDelgate<br />

{<br />

public BinaryOperaion(object target, int methodPtr)<br />

{<br />

...<br />

}<br />

public virtual int Invoke(int argument1, int argument2)<br />

{ ...}<br />

public virtual IAsyncResult BeginInvoke(int argument1, int<br />

argument2, AsyncCallback callback, object asyncState)<br />

{<br />

...<br />

}<br />

public virtual int EndInvoke(IAsyncResult result)<br />

{ ...}<br />

}<br />

2. 同 步 调 用 :<br />

public delegate int BinaryOperation(int argument1, int argument2);<br />

CalCulator calculator = new CalCulator();<br />

BinaryOperaion oppDel = calculator.Add;<br />

oppDel(2,3);<br />

3 . 异 步 调 用 :<br />

首 先 说 明 下 BeginInvoke() 方 法 返 回 的 IAsyncResult 接 口 对 象 , 在 .Net 中<br />

IAsyncResult 的 定 义 如 下 :<br />

// 摘 要 :<br />

// 表 示 异 步 操 作 的 状 态 。<br />

[ComVisible(true)]<br />

public interface IAsyncResult<br />

{<br />

// 摘 要 :<br />

// 获 取 用 户 定 义 的 对 象 , 它 限 定 或 包 含 关 于 异 步 操 作 的 信 息 。<br />

//<br />

// 返 回 结 果 :<br />

// 用 户 定 义 的 对 象 , 它 限 定 或 包 含 关 于 异 步 操 作 的 信 息 。


}<br />

}<br />

object AsyncState { get; }<br />

//<br />

// 摘 要 :<br />

// 获 取 用 于 等 待 异 步 操 作 完 成 的 System.Threading.WaitHandle。<br />

//<br />

// 返 回 结 果 :<br />

// 用 于 等 待 异 步 操 作 完 成 的 System.Threading.WaitHandle。<br />

WaitHandle AsyncWaitHandle { get; }<br />

//<br />

// 摘 要 :<br />

// 获 取 异 步 操 作 是 否 同 步 完 成 的 指 示 。<br />

//<br />

// 返 回 结 果 :<br />

// 如 果 异 步 操 作 同 步 完 成 , 则 为 true; 否 则 为 false。<br />

bool CompletedSynchronously { get; }<br />

//<br />

// 摘 要 :<br />

// 获 取 异 步 操 作 是 否 已 完 成 的 指 示 。<br />

//<br />

// 返 回 结 果 :<br />

// 如 果 操 作 完 成 则 为 true, 否 则 为 false。<br />

bool IsCompleted { get; }<br />

从 它 的 定 义 我 们 可 以 看 出 很 多 东 西 , 如 获 取 异 步 操 作 是 否 同 步 完 成 的 指 示 , 我 们 可 以 把<br />

IAsyncResult 对 象 传 递 给 EndInvoke()<br />

方 法 , 来 标 识 你 希 望 检 索 的 那 个 特 定 的 异 步 方 法 ; 如 下 所 示 :<br />

CalCulator calculator = new CalCulator();<br />

BinaryOperaion oppDel = calculator.Add;<br />

IAsyncResult asynResult = oppDel.BeginInvoke(2, 3, null, null);<br />

int iResult=oppDel.EndInvoke(asynResult);<br />

System.Diagnostics.Debug.Assert(iResult==5);<br />

虽 然 例 子 很 简 单 , 但 是 要 运 用 到 实 际 的 程 序 编 写 中 的 话 , 还 是 要 多 下 点 苦 功 夫 的 , 在 上 面 有 几 个<br />

关 键 点 , 其 中 使 用 EndInvoke() 方 法 我 们 可 以 获 取 所 有 输 出 参 数 和 方 法 的 返 回 值 , 它 堵 塞 了 调<br />

用 者 一 直 到 它 等 待 的 对 象 返 回 。 这 里 有 几 个 需 要 注 意 的 地 方 :<br />

首 先 :EndInvoke() 方 法 在 每 次 调 用 异 步 操 作 时 只 能 执 行 一 次 ;<br />

其 次 : 当 使 用 委 托 进 行 异 步 调 用 时 , 委 托 内 部 列 表 之 允 许 一 个 目 标 方 法 ;<br />

最 后 , 在 将 IAsynResult 对 象 传 递 给 EndInvoke() 方 法 时 , 委 托 和 IAsynResult 对 象 必 须<br />

要 匹 配 。


三 : 轮 询 或 等 待 完 成<br />

有 的 时 候 客 户 端 只 是 想 知 道 某 个 操 作 是 否 完 成 了 , 或 者 想 等 待 一 段 时 间 然 后 再 做 一 些 有 限 的 处<br />

理 , 然 后 再 继 续 等 待 或 处 理 。 这 个 时 候 我 们 可 以 使 用 IAsynResult 的 AsyncWaitHandle 属 性<br />

进 行 堵 塞 , 一 直 到 方 法 完 成<br />

, 方 式 如 下 :asyncResult.AsyncWaitHandle.WaitOne(); 当 然 也 可 以 指 定 超 时<br />

; 其 实 在 有 的 时 候 我 们 有 很 多 方 法 需 要 在 异 步 环 境 下 进 行 的 时 候 我 们 可 以 这 样 来 等 待 多 个 方 法 的<br />

完 成 , 我 们 可 以 获 取 到 一 个 WaitHandle 数 组 , 然 后 调 用 它 的 WaitAll() 方 法 等 待 多 个 异 步 方<br />

法 的 完 成 , 这 个 在 管 理 多 个 异 步 方 法 尤 其 有 优 势 。<br />

总 结 :<br />

由 于 篇 幅 有 限 , 还 有 些 没 讲 的 怎 么 详 细 , 希 望 在 下 篇 能 分 开 慢 慢 道 来 , 在 进 行 异 步 的 过 程 中 , 其<br />

实 对 于 怎 样 完 成 回 调 方 法 也 是 很 有 用 的 , 我 们 可 以 在 回 调 方 法 中 进 行 我 们 需 要 的 一 些 处 理 , 如 发<br />

出 通 知 给 客 户 端 ( 这 个 过 程 应 该 是 事 件 驱 动 的 )。<br />

自 己 其 实 也 只 是 在 一 个 自 动 更 新 模 块 中 用 到 了 一 些 , 理 解 肯 定 还 不 深 , 还 是 那 句 话 , 多 多 指 教 。<br />

posted on 2007-09-15 12:58 寒 蝉 阅 读 (339) 评 论 (0) 编 辑 收 藏 所 属 分 类 : C# 读 书<br />

笔 记<br />

Copyright ©2007 寒 蝉 Powered by: 博 客 园 模 板 提 供 : 沪 江 博 客


C# 的 多 线 程 能 力<br />

楼 主 celineshi()2006-11-08 14:40:47 在 专 题 开 发 / 技 术 / 项 目 / 英 特 尔 多 核 计 算 技 术<br />

提 问<br />

线 程 是 允 许 进 行 并 行 计 算 的 一 个 抽 象 概 念 : 在 另 一 个 线 程 完 成 计 算 任 务 的 同 时 , 一 个 线 程 可<br />

以 对 图 像 进 行 更 新 , 二 个 线 程 可 以 同 时 处 理 同 一 个 进 程 发 出 的 二 个 网 络 请 求 。 我 们 在 这 篇 文<br />

章 中 将 重 点 讨 论 Java 和 C# 在 线 程 方 面 的 不 同 之 处 , 并 将 一 些 Java 中 线 程 的 常 用 模 式 转 换<br />

为 C#。<br />

从 概 念 上 讲 , 线 程 提 供 了 一 种 在 一 个 软 件 中 并 行 执 行 代 码 的 方 式 ━━ 每 个 线 程 都 “ 同 时 ”<br />

在 一 个 共 享 的 内 存 空 间 中 执 行 指 令 ,( 当 然 是 在 一 个 处 理 器 上 , 这 是 通 过 处 于 运 行 状 态 的 线<br />

程 的 交 替 执 行 完 成 的 。), 因 此 , 每 个 线 程 都 可 以 访 问 一 个 程 序 内 的 数 据 结 构 。 由 于 这 种 原<br />

因 , 多 线 程 编 程 的 难 度 就 可 想 而 知 了 , 因 为 一 个 程 序 内 有 许 多 不 同 的 线 程 需 要 安 全 地 共 享 数<br />

据 。<br />

线 程 的 创 建 和 运 行<br />

Java 在 java.lang.Thread 和 java.lang.Runnable 类 中 提 供 了 大 部 分 的 线 程 功 能 。 创<br />

建 一 个 线 程 非 常 简 单 , 就 是 扩 展 Thread 类 , 并 调 用 start()。 通 过 创 建 一 个 执 行 Runnable()<br />

的 类 , 并 将 该 类 作 为 参 数 传 递 给 Thread(), 也 可 以 定 义 一 个 线 程 。 仔 细 地 阅 读 下 面 这 个 简<br />

单 的 Java 程 序 , 其 中 有 2 个 线 程 同 时 在 从 1 数 到 5, 并 将 结 果 打 印 出 来 。<br />

public class ThreadingExample<br />

extends Object {<br />

public static void main( String args[] ) {<br />

Thread[] threads = new Thread[2];<br />

for( int count=1;count


}<br />

我 们 可 以 使 用 System.Threading.Thread 和 System.Threading.ThreadStart 二 个 类 将<br />

上 述 的 Java 程 序 转 换 为 C# 语 言 :<br />

using System.Threading;<br />

public class ThreadingExample : Object {<br />

public static void Main() {<br />

Thread[] threads = new Thread[2];<br />

for( int count=1;count


isAlive() 方 法<br />

IsAlive 获 取 属 性<br />

如 果 该 线 程 处 于 活 动 状 态 , 则 返 回 真 值 。<br />

interrupt() 方 法<br />

Interrupt() 方 法<br />

尽 管 在 Java 中 这 一 方 法 可 以 用 来 设 置 线 程 的 中 断 状 态 , 而 且 可 以 用 来 检 查 线 程 是 否 被<br />

中 断 。 在 C# 中 没 有 相 应 的 方 法 , 对 一 个 没 有 处 于 阻 塞 状 态 的 线 程 执 行 Interrupt 方 法 将 使<br />

下 一 次 阻 塞 调 用 自 动 失 效 。<br />

isInterrupted() 方 法<br />

n/a<br />

如 果 该 线 程 处 于 阻 塞 状 态 , 则 返 回 真 值 。<br />

sleep( long millis ) 和 sleep( long millis, int nanos )<br />

Sleep( int millisecondTimeout ) and Sleep( System.TimeSpan )<br />

方 法<br />

使 正 在 执 行 的 线 程 暂 停 一 段 给 定 的 时 间 , 或 直 到 它 被 中 断 。 这 一 方 法 将 在 Java 中 将 产<br />

生 一 个 java.lang.InterruptedException 状 态 , 在 C# 中 将 产 生<br />

System.Threading. ThreadInterruptedException 状 态 。<br />

join()、join( long millis ) 和<br />

join( long millis, int nanos ) 方 法<br />

Join()、Join( int millisecondTimeout ) 和<br />

Join( System.TimeSpan ) 方 法 与 Java 中 仅 依 靠 超 时 设 定 不 同 的 是 , 在 C# 语 言<br />

中 则 依 据 线 程 停 止 运 行 是 由 于 线 程 死 亡 ( 返 回 真 ) 或 是 超 时 ( 返 回 假 ) 而 返 回 一 个 布 尔 型 变<br />

量 。<br />

suspend() 方 法<br />

Suspend() 方 法<br />

二 者 的 功 能 相 同 。 这 一 方 法 容 易 引 起 死 循 环 , 如 果 一 个 占 有 系 统 关 健 资 源 的 线 程 被 挂 起<br />

来 , 则 在 这 一 线 程 恢 复 运 行 之 前 , 其 他 的 线 程 不 能 访 问 该 资 源 。<br />

resume() 方 法<br />

Resume() 方 法<br />

恢 复 一 个 被 挂 起 的 线 程 。<br />

stop() 方 法<br />

Abort() 方 法<br />

参 见 下 面 的 “ 线 程 停 止 ” 部 分 。<br />

( 特 别 说 明 , 在 上 面 的 表 中 , 每 个 小 节 的 第 一 行 是 java 中 的 方 法 , 第 二 行 是 C# 中 的 方<br />

法 , 第 三 行 是 有 关 的 注 释 , 由 于 在 文 本 文 件 中 不 能 组 织 表 格 , 请 编 辑 多 费 点 心 组 织 表 格 , 原<br />

文 中 有 表 格 的 格 式 。)


线 程 的 中 止<br />

由 于 能 够 在 没 有 任 何 征 兆 的 情 况 下 使 运 行 的 程 序 进 入 一 种 混 乱 的 状 态 ,Java 中 的<br />

Thread.stop 受 到 了 普 遍 的 反 对 。 根 据 所 调 用 的 stop() 方 法 , 一 个 未 经 检 查 的<br />

java.lang.ThreadDeath 错 误 将 会 破 坏 正 在 运 行 着 的 程 序 的 栈 , 随 着 它 的 不 断 运 行 , 能 够 解<br />

除 任 何 被 锁 定 的 对 象 。 由 于 这 些 锁 被 不 分 青 红 皂 白 地 被 打 开 , 由 它 们 所 保 护 的 数 据 就 非 常 可<br />

能 陷 入 混 乱 状 态 中 。<br />

根 据 当 前 的 Java 文 档 , 推 荐 的 中 止 一 个 线 程 的 方 法 是 让 运 行 的 线 程 检 查 一 个 由 其 他 的<br />

线 程 能 够 改 变 的 变 量 , 该 变 量 代 表 一 个 “ 死 亡 时 间 ” 条 件 。 下 面 的 程 序 就 演 示 了 这 种 方 法 。<br />

// 条 件 变 量<br />

private boolean timeToDie = false;<br />

// 在 每 次 迭 代 中 对 条 件 变 量 进 行 检 查 。<br />

class StoppableRunnable<br />

extends Runnable {<br />

public void run() {<br />

while( !timeToDie ) {<br />

// 进 行 相 应 的 操 作<br />

}<br />

}<br />

}<br />

上 述 的 讨 论 对 C# 中 的 Abort 方 法 也 适 合 。 根 据 调 用 的 Abort 方 法 , 令 人 捉 摸 不 定 的<br />

System.Threading.ThreadAbortException 可 能 会 破 坏 线 程 的 栈 , 它 可 能 释 放 线 程 保 持 的 一<br />

些 变 量 , 使 处 于 保 护 状 态 中 的 数 据 结 构 出 现 不 可 预 测 的 错 误 。 我 建 议 使 用 与 上 面 所 示 的 相 似<br />

的 方 法 来 通 知 一 个 应 该 死 亡 的 线 程 。<br />

线 程 的 同 步<br />

从 概 念 上 来 看 , 线 程 非 常 易 于 理 解 , 实 际 上 , 由 于 他 们 可 能 交 互 地 对 同 一 数 据 结 构 进 行<br />

操 作 , 因 此 它 们 成 为 了 令 编 程 人 员 头 疼 的 一 种 东 西 。 以 本 文 开 始 的 ThreadingExample 为 例 ,<br />

当 它 运 行 时 , 会 在 控 制 台 上 输 出 多 种 不 同 的 结 果 。 从 1 2 3 4 5 1 2 3 4 5<br />

到 1 1 2 2 3 3 4 4 5 5 或 1 2 1 2 3 3 4 5 4 5 在 内<br />

的 各 种 情 况 都 是 可 能 出 现 的 , 输 出 结 果 可 能 与 操 作 系 统 的 线 程 调 度 方 式 之 间 的 差 别 有 关 。 有<br />

时 , 需 要 确 保 只 有 一 个 线 程 能 够 访 问 一 个 给 定 的 数 据 结 构 , 以 保 证 数 据 结 构 的 稳 定 , 这 也 是<br />

我 们 需 要 线 程 同 步 机 制 的 原 因 所 在 。<br />

为 了 保 证 数 据 结 构 的 稳 定 , 我 们 必 须 通 过 使 用 “ 锁 ” 来 调 整 二 个 线 程 的 操 作 顺 序 。 二 种<br />

语 言 都 通 过 对 引 用 的 对 象 申 请 一 个 “ 锁 ”, 一 旦 一 段 程 序 获 得 该 “ 锁 ” 的 控 制 权 后 , 就 可 以<br />

保 证 只 有 它 获 得 了 这 个 “ 锁 ”, 能 够 对 该 对 象 进 行 操 作 。 同 样 , 利 用 这 种 锁 , 一 个 线 程 可 以<br />

一 直 处 于 等 待 状 态 , 直 到 有 能 够 唤 醒 它 信 号 通 过 变 量 传 来 为 止 。<br />

表 2: 线 程 同 步<br />

需 要 对 线 程 进 行 同 步 时 需 要 掌 握 的 关 健 字<br />

synchronized<br />

lock


C# 中 的 lock 命 令 实 际 上 是 为 使 用 System.Threading.Monitor 类 中 的 Enter 和 Exit 方 法 的<br />

语 法 上 的 准 备<br />

Object.wait()<br />

Monitor.Wait( object obj )<br />

C# 中 没 有 等 待 对 象 的 方 法 , 如 果 要 等 待 一 个 信 号 , 则 需 要 使 用 System.Threading.Monitor<br />

类 , 这 二 个 方 法 都 需 要 在 同 步 的 程 序 段 内 执 行 。<br />

Object.notify()<br />

Monitor.Pulse( object obj )<br />

参 见 上 面 的 Monitor.Wait 的 注 释 。<br />

Object.notify()<br />

Monitor.PulseAll( object obj )<br />

参 见 上 面 的 Monitor.Wait 的 注 释 。<br />

( 特 别 说 明 , 在 上 面 的 表 中 , 每 个 小 节 的 第 一 行 是 java 中 的 方 法 , 第 二 行 是 C# 中 的 方<br />

法 , 第 三 行 是 有 关 的 注 释 , 由 于 在 文 本 文 件 中 不 能 组 织 表 格 , 请 编 辑 多 费 点 心 组 织 表 格 , 原<br />

文 中 有 表 格 的 格 式 。)<br />

我 们 可 以 对 上 面 的 例 子 进 行 一 些 适 当 的 修 改 , 通 过 首 先 添 加 一 个 进 行 同 步 的 变 量 , 然 后<br />

对 count() 方 法 进 行 如 下 的 修 改 , 使 变 量 在 “ 锁 ” 中 被 执 行 加 1 操 作 。<br />

public static Object synchronizeVariable = "locking variable";<br />

public static void count() {<br />

synchronized( synchronizeVariable ) {<br />

for( int count=1;count


synchronizeVariable 的 线 程 ( 尽 管 现 在 还 没 有 线 程 处 于 等 待 状 态 。), 并 试 图 获 得 被 锁 着 的 变<br />

量 , 然 后 等 待 再 次 获 得 锁 变 量 ; 下 一 个 线 程 就 可 以 开 始 执 行 for loop 循 环 输 出 数 字 1, 调<br />

用 notifyAll() 唤 醒 前 面 的 线 程 , 并 使 它 开 始 试 图 获 得 synchronizeVariable 变 量 , 使 自 己 处 于<br />

等 待 状 态 , 释 放 synchronizeVariable, 允 许 前 面 的 线 程 获 得 它 。 这 个 循 环 将 一 直 进 行 下 去 ,<br />

直 到 它 们 都 输 出 完 从 1 到 5 的 数 字 。<br />

通 过 一 些 简 单 的 语 法 变 化 可 以 将 上 述 的 修 改 在 C# 中 实 现 :<br />

public static Object synchronizeVariable = "locking variable";<br />

public static void count() {<br />

lock( synchronizeVariable ) {<br />

for( int count=1;count


解 决 的 , 我 们 来 看 一 下 下 面 的 Java 代 码 :<br />

public static int x = 1;<br />

public static void increment() {<br />

x = x 1;<br />

}<br />

如 果 有 二 个 不 同 的 线 程 同 时 调 用 increment(),x 最 后 的 值 可 能 是 2 或 3, 发 生 这 种 情 况<br />

的 原 因 可 能 是 二 个 进 程 无 序 地 访 问 x 变 量 , 在 没 有 将 x 置 初 值 时 对 它 执 行 加 1 操 作 ; 在 任 一<br />

线 程 有 机 会 对 x 执 行 加 1 操 作 之 前 , 二 个 线 程 都 可 能 将 x 读 作 1, 并 将 它 设 置 为 新 的 值 。<br />

在 Java 和 C# 中 , 我 们 都 可 以 实 现 对 x 变 量 的 同 步 访 问 , 所 有 进 程 都 可 以 按 各 自 的 方 式<br />

运 行 。 但 通 过 使 用 Interlocked 类 ,C# 提 供 了 一 个 对 这 一 问 题 更 彻 底 的 解 决 方 案 。Interlocked<br />

类 有 一 些 方 法 , 例 如 Increment( ref int location ) 、<br />

Decrement( ref int location ), 这 二 个 方 法 都 取 得 整 数 型 参 数 , 对 该 整 数 执 行 加 或 减<br />

1 操 作 , 并 返 回 新 的 值 , 所 有 这 些 操 作 都 以 “ 不 可 分 割 的 ” 方 式 进 行 , 这 样 就 无 需 单 独 创 建<br />

一 个 可 以 进 行 同 步 操 作 的 对 象 , 如 下 例 所 示 :<br />

public static Object locker = ...<br />

public static int x = 1;<br />

public static void increment() {<br />

synchronized( locker ) {<br />

x = x 1;<br />

}<br />

}<br />

C# 中 的 Interlocked 类 可 以 用 下 面 的 代 码 完 成 相 同 的 操 作 :<br />

public static int x = 1;<br />

public static void Increment() {<br />

Interlocked.Increment( ref x );<br />

}<br />

Interlocked 中 还 包 括 一 个 名 字 为 Exchange 的 方 法 , 可 以 “ 不 可 分 割 ” 地 将 一 个 变 量 的 值<br />

设 置 为 另 一 个 变 量 的 值 。<br />

线 程 池<br />

如 果 许 多 利 用 了 线 程 的 应 用 软 件 都 创 建 线 程 , 这 些 线 程 将 会 因 等 待 某 些 条 件 ( 键 盘 或 新<br />

的 I/O 输 入 等 ) 而 在 等 待 状 态 中 浪 费 大 部 分 的 时 间 ,C# 提 供 的 System.Threading.ThreadPool<br />

对 象 可 以 解 决 这 一 问 题 。 使 用 ThreadPool 和 事 件 驱 动 的 编 程 机 制 , 程 序 可 以 注 册 一 个<br />

System.Threading.WaitHandle 对 象 (WaitHandle 是 C# 编 程 中 等 待 和 通 知 机 制 的 对 象 模 型 。)<br />

和 System.Threading.WaitOrTimerCallback 对 象 , 所 有 的 线 程 无 需 自 己 等 待 WaitHandle 的 释<br />

放 ,ThreadPool 将 监 控 所 有 向 它 注 册 的 WaitHandle, 然 后 在 WaitHandle 被 释 放 后 调 用 相 应<br />

WaitOrTimerCallback 对 象 的 方 法 。<br />

结 束 语<br />

在 本 篇 文 章 中 我 们 简 单 地 讨 论 了 C# 提 供 的 用 于 线 程 和 并 行 操 作 的 机 制 , 其 中 的 大 部 分<br />

与 Java 相 似 ━━C# 提 供 了 可 以 运 行 提 供 的 方 法 的 Thread 对 象 , 同 时 提 供 了 对 代 码 访 问 进 行<br />

同 步 的 方 法 。 与 在 其 他 方 面 一 样 ,C# 在 线 程 方 面 也 提 供 了 一 些 Java 不 支 持 的 语 法 ( 在 一 定<br />

程 度 上 , 揭 示 了 同 步 操 作 的 一 些 底 层 的 内 容 。),Java 编 程 人 员 可 能 会 发 现 这 一 部 分 非 常 有<br />

用 。


56HC# 异 步 编 程<br />

同 步 方 法 和 异 步 方 法 的 区 别<br />

同 步 方 法 调 用 在 程 序 继 续 执 行 之 前 需 要 等 待 同 步 方 法 执 行 完 毕 返 回 结 果<br />

异 步 方 法 则 在 被 调 用 之 后 立 即 返 回 以 便 程 序 在 被 调 用 方 法 完 成 其 任 务 的 同 时 执 行 其 它 操 作<br />

异 步 编 程 概 览<br />

.NET Framework 允 许 您 异 步 调 用 任 何 方 法 。 定 义 与 您 需 要 调 用 的 方 法 具 有 相 同 签 名 的 委 托 ;<br />

公 共 语 言 运 行 库 将 自 动 为 该 委 托 定 义 具 有 适 当 签 名<br />

的 BeginInvoke 和 EndInvoke 方 法 。<br />

BeginInvoke 方 法 用 于 启 动 异 步 调 用 。 它 与 您 需 要 异 步 执 行 的 方 法 具 有 相 同 的 参 数 , 只 不 过<br />

还 有 两 个 额 外 的 参 数 ( 将 在 稍 后 描 述 )。<br />

BeginInvoke 立 即 返 回 , 不 等 待 异 步 调 用 完 成 。<br />

BeginInvoke 返 回 IasyncResult, 可 用 于 监 视 调 用 进 度 。<br />

EndInvoke 方 法 用 于 检 索 异 步 调 用 结 果 。 调 用 BeginInvoke 后 可 随 时 调 用 EndInvoke 方<br />

法 ; 如 果 异 步 调 用 未 完 成 ,EndInvoke 将 一 直 阻 塞 到<br />

异 步 调 用 完 成 。EndInvoke 的 参 数 包 括 您 需 要 异 步 执 行 的 方 法 的 out 和 ref 参 数 ( 在<br />

Visual Basic 中 为 ByRef 和 ByRef) 以 及 由<br />

BeginInvoke 返 回 的 IAsyncResult。<br />

四 种 使 用 BeginInvoke 和 EndInvoke 进 行 异 步 调 用 的 常 用 方 法 。 调 用 了 BeginInvoke 后 ,<br />

可 以 :<br />

1. 进 行 某 些 操 作 , 然 后 调 用 EndInvoke 一 直 阻 塞 到 调 用 完 成 。<br />

2. 使 用 IAsyncResult.AsyncWaitHandle 获 取 WaitHandle, 使 用 它 的 WaitOne 方 法 将 执 行<br />

一 直 阻 塞 到 发 出 WaitHandle 信 号 , 然 后 调 用<br />

EndInvoke。 这 里 主 要 是 主 程 序 等 待 异 步 方 法 , 等 待 异 步 方 法 的 结 果 。<br />

3. 轮 询 由 BeginInvoke 返 回 的 IAsyncResult,IAsyncResult.IsCompeted 确 定 异 步 调 用 何<br />

时 完 成 , 然 后 调 用 EndInvoke。 此 处 理 个 人 认 为 与<br />

相 同 。<br />

4. 将 用 于 回 调 方 法 的 委 托 传 递 给 BeginInvoke。 该 方 法 在 异 步 调 用 完 成 后 在 ThreadPool 线<br />

程 上 执 行 , 它 可 以 调 用 EndInvoke。 这 是 在 强 制 装


换 回 调 函 数 里 面 IAsyncResult.AsyncState(BeginInvoke 方 法 的 最 后 一 个 参 数 ) 成 委 托 , 然<br />

后 用 委 托 执 行 EndInvoke。<br />

警 告 始 终 在 异 步 调 用 完 成 后 调 用 EndInvoke。<br />

以 上 有 不 理 解 的 稍 后 可 以 再 理 解 。<br />

例 子<br />

1) 先 来 个 简 单 的 没 有 回 调 函 数 的 异 步 方 法 例 子<br />

请 再 运 行 程 序 的 时 候 , 仔 细 看 注 释 , 对 理 解 很 有 帮 助 。 还 有 , 若 将 注 释 的 中 的 两<br />

个 方 法 都 同 步 , 你 会 发 现 异 步 运 行 的 速 度 优 越 性 。<br />

using System;<br />

namespace ConsoleApplication1<br />

{<br />

class Class1<br />

{<br />

// 声 明 委 托<br />

public delegate void AsyncEventHandler();<br />

// 异 步 方 法<br />

void Event1()<br />

{<br />

Console.WriteLine("Event1 Start");<br />

System.Threading.Thread.Sleep(4000);<br />

Console.WriteLine("Event1 End");


}<br />

// 同 步 方 法<br />

void Event2()<br />

{<br />

Console.WriteLine("Event2 Start");<br />

int i=1;<br />

while(i


实 例 委 托<br />

AsyncEventHandler asy = new AsyncEventHandler(c.Event1);<br />

// 异 步 调 用 开 始 , 没 有 回 调 函 数 和 AsyncState, 都 为 null<br />

IAsyncResult ia = asy.BeginInvoke(null, null);<br />

// 同 步 开 始 ,<br />

c.Event2();<br />

回 值 。<br />

// 异 步 结 束 , 若 没 有 结 束 , 一 直 阻 塞 到 调 用 完 成 , 在 此 返 回 该 函 数 的 return, 若 有 返<br />

asy.EndInvoke(ia);<br />

// 都 同 步 的 情 况 。<br />

//c.Event1();<br />

//c.Event2();<br />

end =DateTime.Now.Ticks;<br />

Console.WriteLine(" 时 间 刻 度 差 ="+ Convert.ToString(end-start) );<br />

Console.ReadLine();<br />

}<br />

}<br />

}<br />

2) 下 面 看 有 回 调 函 数 的 WebRequest 和 WebResponse 的 异 步 操 作 。


using System;<br />

using System.Net;<br />

using System.Threading;<br />

using System.Text;<br />

using System.IO;<br />

// RequestState 类 用 于 通 过<br />

// 异 步 调 用 传 递 数 据<br />

public class RequestState<br />

{<br />

const int BUFFER_SIZE = 1024;<br />

public StringBuilder RequestData;<br />

public byte[] BufferRead;<br />

public HttpWebRequest Request;<br />

public Stream ResponseStream;<br />

// 创 建 适 当 编 码 类 型 的 解 码 器<br />

public Decoder StreamDecode = Encoding.UTF8.GetDecoder();<br />

public RequestState()<br />

{<br />

BufferRead = new byte[BUFFER_SIZE];<br />

RequestData = new StringBuilder("");<br />

Request = null;


ResponseStream = null;<br />

}<br />

}<br />

// ClientGetAsync 发 出 异 步 请 求<br />

class ClientGetAsync<br />

{<br />

public static ManualResetEvent allDone = new ManualResetEvent(false);<br />

const int BUFFER_SIZE = 1024;<br />

public static void Main(string[] args)<br />

{<br />

if (args.Length < 1)<br />

{<br />

showusage();<br />

return;<br />

}<br />

// 从 命 令 行 获 取 URI<br />

Uri HttpSite = new Uri(args[0]);<br />

// 创 建 请 求 对 象<br />

HttpWebRequest wreq = (HttpWebRequest)WebRequest.Create(HttpSite);<br />

// 创 建 状 态 对 象


RequestState rs = new RequestState();<br />

// 将 请 求 添 加 到 状 态 , 以 便 它 可 以 被 来 回 传 递<br />

rs.Request = wreq;<br />

// 发 出 异 步 请 求<br />

lback), rs);<br />

IAsyncResult r = (IAsyncResult)wreq.BeginGetResponse(new AsyncCallback(RespCal<br />

// 将 ManualResetEvent 设 置 为 Wait,<br />

// 以 便 在 调 用 回 调 前 , 应 用 程 序 不 退 出<br />

allDone.WaitOne();<br />

}<br />

public static void showusage()<br />

{<br />

Console.WriteLine(" 尝 试 获 取 (GET) 一 个 URL");<br />

Console.WriteLine("\r\n 用 法 ::");<br />

Console.WriteLine("ClientGetAsync URL");<br />

Console.WriteLine(" 示 例 ::");<br />

Console.WriteLine("ClientGetAsync http://www.microsoft.com/net/");<br />

}<br />

private static void RespCallback(IAsyncResult ar)<br />

{<br />

// 从 异 步 结 果 获 取 RequestState 对 象


RequestState rs = (RequestState)ar.AsyncState;<br />

// 从 RequestState 获 取 HttpWebRequest<br />

HttpWebRequest req = rs.Request;<br />

// 调 用 EndGetResponse 生 成 HttpWebResponse 对 象<br />

// 该 对 象 来 自 上 面 发 出 的 请 求<br />

HttpWebResponse resp = (HttpWebResponse)req.EndGetResponse(ar);<br />

// 既 然 我 们 拥 有 了 响 应 , 就 该 从<br />

// 响 应 流 开 始 读 取 数 据 了<br />

Stream ResponseStream = resp.GetResponseStream();<br />

// 该 读 取 操 作 也 使 用 异 步 完 成 , 所 以 我 们<br />

// 将 要 以 RequestState 存 储 流<br />

rs.ResponseStream = ResponseStream;<br />

// 请 注 意 ,rs.BufferRead 被 传 入 到 BeginRead。<br />

// 这 是 数 据 将 被 读 入 的 位 置 。<br />

IAsyncResult iarRead = ResponseStream.BeginRead(rs.BufferRead, 0, BUFFER_SIZ<br />

E, new AsyncCallback(ReadCallBack), rs);<br />

}<br />

private static void ReadCallBack(IAsyncResult asyncResult)<br />

{<br />

// 从 asyncresult 获 取 RequestState 对 象


RequestState rs = (RequestState)asyncResult.AsyncState;<br />

// 取 出 在 RespCallback 中 设 置 的 ResponseStream<br />

Stream responseStream = rs.ResponseStream;<br />

// 此 时 rs.BufferRead 中 应 该 有 一 些 数 据 。<br />

// 读 取 操 作 将 告 诉 我 们 那 里 是 否 有 数 据<br />

int read = responseStream.EndRead(asyncResult);<br />

if (read > 0)<br />

{<br />

// 准 备 Char 数 组 缓 冲 区 , 用 于 向 Unicode 转 换<br />

Char[] charBuffer = new Char[BUFFER_SIZE];<br />

// 将 字 节 流 转 换 为 Char 数 组 , 然 后 转 换 为 字 符 串<br />

// len 显 示 多 少 字 符 被 转 换 为 Unicode<br />

int len = rs.StreamDecode.GetChars(rs.BufferRead, 0, read, charBuffer, 0);<br />

String str = new String(charBuffer, 0, len);<br />

// 将 最 近 读 取 的 数 据 追 加 到 RequestData stringbuilder 对 象 中 ,<br />

// 该 对 象 包 含 在 RequestState 中<br />

rs.RequestData.Append(str);<br />

// 现 在 发 出 另 一 个 异 步 调 用 , 读 取 更 多 的 数 据<br />

// 请 注 意 , 将 不 断 调 用 此 过 程 , 直 到


esponseStream.EndRead 返 回 -1<br />

IAsyncResult ar = responseStream.BeginRead(rs.BufferRead, 0, BUFFER_SIZE, ne<br />

w AsyncCallback(ReadCallBack), rs);<br />

}<br />

else<br />

{<br />

if (rs.RequestData.Length > 1)<br />

{<br />

// 所 有 数 据 都 已 被 读 取 , 因 此 将 其 显 示 到 控 制 台<br />

string strContent;<br />

strContent = rs.RequestData.ToString();<br />

Console.WriteLine(strContent);<br />

}<br />

// 关 闭 响 应 流<br />

responseStream.Close();<br />

// 设 置 ManualResetEvent, 以 便 主 线 程 可 以 退 出<br />

allDone.Set();<br />

}<br />

return;<br />

}


}<br />

在 这 里 有 回 调 函 数 , 且 异 步 回 调 中 又 有 异 步 操 作 。<br />

首 先 是 异 步 获 得 ResponseStream, 然 后 异 步 读 取 数 据 。<br />

这 个 程 序 非 常 经 典 。 从 中 可 以 学 到 很 多 东 西 的 。 我 们 来 共 同 探 讨 。<br />

总 结<br />

上 面 说 过 ,.net framework 可 以 异 步 调 用 任 何 方 法 。 所 以 异 步 用 处 广 泛 。<br />

在 .net framework 类 库 中 也 有 很 多 异 步 调 用 的 方 法 。 一 般 都 是 已 Begin 开 头 End 结 尾 构 成<br />

一 对 , 异 步 委 托 方 法 , 外 加 两 个 回 调 函 数 和 AsyncState 参 数 , 组 成 异 步 操 作 的 宏 观 体 现 。<br />

所 以 要 做 异 步 编 程 , 不 要 忘 了 委 托 delegate、Begin,End,AsyncCallBack 委 托 ,AsyncState<br />

实 例 ( 在 回 调 函 数 中 通 过 IAsyncResult.AsyncState 来 强 制 转 换 ),IAsycResult( 监 控 异 步 ),<br />

就 足 以 理 解 异 步 真 谛 了 。


57H C#<br />

异 步 数 据 处 理 及 进 度 显 示<br />

对 于 C# 的 事 件 , 指 代 (Delegate) 总 是 感 觉 理 解 不 太 深 刻 。 这 几 天 正 好 有 机 会 学 习<br />

了 一 下 。 从 一 个 程 序 中 改 了 一 部 分 代 码 , 实 现 了 一 个 异 步 数 据 处 理 基 本 构 架 。<br />

using System;<br />

using System.Collections.Generic;<br />

using System.ComponentModel;<br />

using System.Data;<br />

using System.Drawing;<br />

using System.Text;<br />

using System.Windows.Forms;<br />

using System.Threading;<br />

namespace testManager<br />

{<br />

//progressbar 窗 体<br />

// 有 一 个 cancel 按 钮 , 和 一 个 进 度 条<br />

public class ProgressForm : Form<br />

{<br />

l;<br />

private System.ComponentModel.IContainer components = nul<br />

protected override void Dispose(bool disposing)<br />

{


if (disposing && (components != null))<br />

{<br />

components.Dispose();<br />

}<br />

base.Dispose(disposing);<br />

}<br />

Windows InitializeComponent<br />

private System.Windows.Forms.ProgressBar poProgressBar;<br />

private System.Windows.Forms.Button btnCancel;<br />

// 定 义 进 度 状 态 的 事 件 。<br />

public delegate void StatusHandler();<br />

public event StatusHandler StatusChanged;<br />

private int iStatus = 1;//1:normal 0:cancel -1:exception<br />

// 返 回 进 度 状 态<br />

public int GetStatus<br />

{<br />

get { return iStatus; }<br />

}


public ProgressForm()<br />

{<br />

InitializeComponent();<br />

this.poProgressBar.Value = 0;<br />

}<br />

// 进 度 条 加 1<br />

public void PerformStep()<br />

{<br />

try<br />

{<br />

this.poProgressBar.PerformStep();<br />

this.poProgressBar.Refresh();<br />

if (this.poProgressBar.Value == 100)<br />

{<br />

iStatus = 1;<br />

this.StatusChanged();<br />

}<br />

}<br />

catch<br />

{


iStatus = -1;<br />

StatusChanged();<br />

}<br />

}<br />

private void btnCancel_Click(object sender, EventArgs e)<br />

{<br />

this.iStatus = 0;<br />

StatusChanged();<br />

}<br />

}<br />

// 逻 辑 控 制 类 , 相 当 于 主 控 部 分<br />

public class ProgressManager<br />

{<br />

// 实 际 进 行 运 算 处 理 的 类 实 例<br />

private IRunAble poRun;<br />

// 进 度 条 类 实 例<br />

private ProgressForm poProgress;<br />

public delegate void CompletedHandler();<br />

public event CompletedHandler ProgressCompleted;


public int GetStatus<br />

{<br />

get { return poProgress.GetStatus; }<br />

}<br />

public ProgressManager(IRunAble RunAbleInstance)<br />

{<br />

this.poRun = RunAbleInstance;<br />

poProgress = new ProgressForm();<br />

terScreen;<br />

this.poProgress.StartPosition = FormStartPosition.Cen<br />

this.poProgress.Show();<br />

poProgress.StatusChanged += new ProgressForm.StatusHa<br />

ndler(poProgress_StatusChanged);<br />

}<br />

public void Start()<br />

{<br />

// 异 步 调 用 , 进 行 运 算 。<br />

n);<br />

AsyncDelegate oDelegate = new AsyncDelegate(DoFunctio<br />

oDelegate.BeginInvoke(myCallBack, new object());<br />

}<br />

private delegate void AsyncDelegate();


private void DoFunction()<br />

{<br />

this.poRun.Run(this);<br />

}<br />

// 回 调 函 数<br />

private void myCallBack(IAsyncResult iResult)<br />

{<br />

this.poProgress.Close();<br />

}<br />

public void PerformStep()<br />

{<br />

this.poProgress.PerformStep();<br />

}<br />

void poProgress_StatusChanged()<br />

{<br />

== 1)<br />

if (poProgress.GetStatus == 0 || poProgress.GetStatus<br />

{<br />

poProgress.Close();


}<br />

ProgressCompleted();<br />

}<br />

}<br />

// 实 际 运 算 类 接 口<br />

public interface IRunAble<br />

{<br />

void Run(ProgressManager aoManager);<br />

}<br />

// 实 际 运 算 外 理 类<br />

public class CalRun : IRunAble<br />

{<br />

public void Run(ProgressManager aoManager)<br />

{<br />

double p = 3.1415926;<br />

for (int i = 1; i


System.Windows.Forms.Application.DoEvents();<br />

double dd = i * i * p;<br />

56);<br />

);<br />

double dd2 = Math.Abs(dd * 456) * Math.Abs(dd * 4<br />

double dd3 = Math.Pow(dd, dd2) * Math.Pow(dd, dd2<br />

if (i % 1000 == 0)<br />

{<br />

aoManager.PerformStep();<br />

}<br />

}<br />

}<br />

}<br />

}<br />

调 用 方 式 如 下 :<br />

private CalRun cr;<br />

private ProgressManager pm;<br />

private void button1_Click(object sender, EventArgs e)<br />

{<br />

cr = new CalRun();


pm = new ProgressManager(cr);<br />

Handler(pm_ProgressCompleted);<br />

pm.ProgressCompleted += new ProgressManager.Completed<br />

pm.Start();<br />

}<br />

void pm_ProgressCompleted()<br />

{<br />

switch (pm.GetStatus)<br />

{<br />

case 1:<br />

MessageBox.Show("OK");<br />

break;<br />

case 0:<br />

MessageBox.Show("Cancel");<br />

break;<br />

case -1:<br />

MessageBox.Show("Error");<br />

break;<br />

}<br />

}


英 文 原 版 地 址 :58Hhttp://www.sellsbrothers.com/writing/default.aspx?content=delegates.htm<br />

.NET 委 托 : 一 个 C# 睡 前 故 事<br />

英 文 版 原 作 者 :Chris Sells(59Hhttp://www.sellsbrothers.com/)<br />

翻 译 : 袁 晓 辉 (60Hhttp://www.farproc.com/ 61Hhttp://dev.csdn.net/uoyevoli)<br />

紧 耦 合<br />

从 前 , 在 南 方 一 块 奇 异 的 土 地 上 , 有 个 工 人 名 叫 彼 得 , 他 非 常 勤 奋 , 对 他 的<br />

老 板 总 是 百 依 百 顺 。 但 是 他 的 老 板 是 个 吝 啬 的 人 , 从 不 信 任 别 人 , 坚 决 要 求 随 时<br />

知 道 彼 得 的 工 作 进 度 , 以 防 止 他 偷 懒 。 但 是 彼 得 又 不 想 让 老 板 呆 在 他 的 办 公 室 里<br />

站 在 背 后 盯 着 他 , 于 是 就 对 老 板 做 出 承 诺 : 无 论 何 时 , 只 要 我 的 工 作 取 得 了 一 点<br />

进 展 我 都 会 及 时 让 你 知 道 。 彼 得 通 过 周 期 性 地 使 用 “ 带 类 型 的 引 用 ”( 原 文 为 :<br />

“typed reference” 也 就 是 delegate??)“ 回 调 ” 他 的 老 板 来 实 现 他 的 承 诺 ,<br />

如 下 :<br />

class Worker {<br />

public void Advise(Boss boss) { _boss = boss; }<br />

public void DoWork() {<br />

Console.WriteLine(“ 工 作 : 工 作 开 始 ”);<br />

if( _boss != null ) _boss.WorkStarted();<br />

Console.WriteLine(“ 工 作 : 工 作 进 行 中 ”);<br />

if( _boss != null ) _boss.WorkProgressing();<br />

}<br />

Console.WriteLine("“ 工 作 : 工 作 完 成 ”");<br />

if( _boss != null ) {<br />

int grade = _boss.WorkCompleted();<br />

Console.WriteLine(“ 工 人 的 工 作 得 分 =” + grade);<br />

}<br />

}<br />

private Boss _boss;<br />

class Boss {<br />

public void WorkStarted() { /* 老 板 不 关 心 。 */ }<br />

public void WorkProgressing() { /* 老 板 不 关 心 。 */ }<br />

public int WorkCompleted() {<br />

Console.WriteLine(“ 时 间 差 不 多 !”);<br />

return 2; /* 总 分 为 10 */<br />

}<br />

}


class Universe {<br />

static void Main() {<br />

Worker peter = new Worker();<br />

Boss boss = new Boss();<br />

peter.Advise(boss);<br />

peter.DoWork();<br />

}<br />

}<br />

Console.WriteLine(“Main: 工 人 工 作 完 成 ”);<br />

Console.ReadLine();<br />

接 口<br />

现 在 , 彼 得 成 了 一 个 特 殊 的 人 , 他 不 但 能 容 忍 吝 啬 的 老 板 , 而 且 和 他 周 围 的<br />

宇 宙 也 有 了 密 切 的 联 系 , 以 至 于 他 认 为 宇 宙 对 他 的 工 作 进 度 也 感 兴 趣 。 不 幸 的 是 ,<br />

他 必 须 也 给 宇 宙 添 加 一 个 特 殊 的 回 调 函 数 Advise 来 实 现 同 时 向 他 老 板 和 宇 宙 报<br />

告 工 作 进 度 。 彼 得 想 要 把 潜 在 的 通 知 的 列 表 和 这 些 通 知 的 实 现 方 法 分 离 开 来 , 于<br />

是 他 决 定 把 方 法 分 离 为 一 个 接 口 :<br />

interface IWorkerEvents {<br />

void WorkStarted();<br />

void WorkProgressing();<br />

int WorkCompleted();<br />

}<br />

class Worker {<br />

public void Advise(IWorkerEvents events) { _events = events; }<br />

public void DoWork() {<br />

Console.WriteLine(“ 工 作 : 工 作 开 始 ”);<br />

if( _events != null ) _events.WorkStarted();<br />

Console.WriteLine(“ 工 作 : 工 作 进 行 中 ”);<br />

if(_events != null ) _events.WorkProgressing();<br />

Console.WriteLine("“ 工 作 : 工 作 完 成 ”");<br />

if(_events != null ) {<br />

int grade = _events.WorkCompleted();<br />

}<br />

Console.WriteLine(“ 工 人 的 工 作 得 分 =” + grade);<br />

}<br />

}<br />

private IWorkerEvents _events;


class Boss : IWorkerEvents {<br />

public void WorkStarted() { /* 老 板 不 关 心 。 */ }<br />

public void WorkProgressing() { /* 老 板 不 关 心 。 */ }<br />

public int WorkCompleted() {<br />

Console.WriteLine(“ 时 间 差 不 多 !”);<br />

return 3; /* 总 分 为 10 */<br />

}<br />

}<br />

委 托<br />

不 幸 的 是 , 每 当 彼 得 忙 于 通 过 接 口 的 实 现 和 老 板 交 流 时 , 就 没 有 机 会 及 时 通<br />

知 宇 宙 了 。 至 少 他 应 该 忽 略 身 在 远 方 的 老 板 的 引 用 , 好 让 其 他 实 现 了<br />

IWorkerEvents 的 对 象 得 到 他 的 工 作 报 告 。(”At least he'd abstracted the<br />

reference of his boss far away from him so that others who implemented<br />

the IWorkerEvents interface could be notified of his work progress” 原<br />

话 如 此 , 不 理 解 到 底 是 什 么 意 思 :))<br />

他 的 老 板 还 是 抱 怨 得 很 厉 害 。“ 彼 得 !” 他 老 板 吼 道 ,“ 你 为 什 么 在 工 作 一<br />

开 始 和 工 作 进 行 中 都 来 烦 我 ?! 我 不 关 心 这 些 事 件 。 你 不 但 强 迫 我 实 现 了 这 些 方<br />

法 , 而 且 还 在 浪 费 我 宝 贵 的 工 作 时 间 来 处 理 你 的 事 件 , 特 别 是 当 我 外 出 的 时 候 更<br />

是 如 此 ! 你 能 不 能 不 再 来 烦 我 ?”<br />

于 是 , 彼 得 意 识 到 接 口 虽 然 在 很 多 情 况 都 很 有 用 , 但 是 当 用 作 事 件 时 ,“ 粒<br />

度 ” 不 够 好 。 他 希 望 能 够 仅 在 别 人 想 要 时 才 通 知 他 们 , 于 是 他 决 定 把 接 口 的 方 法<br />

分 离 为 单 独 的 委 托 , 每 个 委 托 都 像 一 个 小 的 接 口 方 法 :<br />

delegate void WorkStarted();<br />

delegate void WorkProgressing();<br />

delegate int WorkCompleted();<br />

class Worker {<br />

public void DoWork() {<br />

Console.WriteLine(“ 工 作 : 工 作 开 始 ”);<br />

if( started != null ) started();<br />

Console.WriteLine(“ 工 作 : 工 作 进 行 中 ”);<br />

if( progressing != null ) progressing();<br />

}<br />

Console.WriteLine("“ 工 作 : 工 作 完 成 ”");<br />

if( completed != null ) {<br />

int grade = completed();<br />

Console.WriteLine(“ 工 人 的 工 作 得 分 =” + grade);<br />

}


}<br />

public WorkStarted started;<br />

public WorkProgressing progressing;<br />

public WorkCompleted completed;<br />

class Boss {<br />

public int WorkCompleted() {<br />

Console.WriteLine("Better...");<br />

return 4; /* 总 分 为 10 */<br />

}<br />

}<br />

class Universe {<br />

static void Main() {<br />

Worker peter = new Worker();<br />

Boss boss = new Boss();<br />

peter.completed = new WorkCompleted(boss.WorkCompleted);<br />

peter.DoWork();<br />

}<br />

}<br />

Console.WriteLine(“Main: 工 人 工 作 完 成 ”);<br />

Console.ReadLine();<br />

静 态 监 听 者<br />

这 样 , 彼 得 不 会 再 拿 他 老 板 不 想 要 的 事 件 来 烦 他 老 板 了 , 但 是 他 还 没 有 把 宇<br />

宙 放 到 他 的 监 听 者 列 表 中 。 因 为 宇 宙 是 个 包 涵 一 切 的 实 体 , 看 来 不 适 合 使 用 实 例<br />

方 法 的 委 托 ( 想 像 一 下 , 实 例 化 一 个 “ 宇 宙 ” 要 花 费 多 少 资 源 …..), 于 是 彼 得<br />

就 需 要 能 够 对 静 态 委 托 进 行 挂 钩 , 委 托 对 这 一 点 支 持 得 很 好 :<br />

class Universe {<br />

static void WorkerStartedWork() {<br />

Console.WriteLine("Universe notices worker starting work");<br />

}<br />

static int WorkerCompletedWork() {<br />

Console.WriteLine("Universe pleased with worker's work");<br />

return 7;<br />

}<br />

static void Main() {<br />

Worker peter = new Worker();<br />

Boss boss = new Boss();<br />

peter.completed = new WorkCompleted(boss.WorkCompleted);


peter.started = new WorkStarted(Universe.WorkerStartedWork);<br />

peter.completed = new WorkCompleted(Universe.WorkerCompletedWork);<br />

peter.DoWork();<br />

}<br />

}<br />

Console.WriteLine(“Main: 工 人 工 作 完 成 ”);<br />

Console.ReadLine();<br />

事 件<br />

不 幸 的 是 , 宇 宙 太 忙 了 , 也 不 习 惯 时 刻 关 注 它 里 面 的 个 体 , 它 可 以 用 自 己 的<br />

委 托 替 换 了 彼 得 老 板 的 委 托 。 这 是 把 彼 得 的 Worker 类 的 的 委 托 字 段 做 成 public<br />

的 一 个 无 意 识 的 副 作 用 。 同 样 , 如 果 彼 得 的 老 板 不 耐 烦 了 , 也 可 以 决 定 自 己 来 激<br />

发 彼 得 的 委 托 ( 真 是 一 个 粗 鲁 的 老 板 ):<br />

// Peter's boss taking matters into his own hands<br />

if( peter.completed != null ) peter.completed();<br />

彼 得 不 想 让 这 些 事 发 生 , 他 意 识 到 需 要 给 每 个 委 托 提 供 “ 注 册 ” 和 “ 反 注 册 ”<br />

功 能 , 这 样 监 听 者 就 可 以 自 己 添 加 和 移 除 委 托 , 但 同 时 又 不 能 清 空 整 个 列 表 也 不<br />

能 随 意 激 发 彼 得 的 事 件 了 。 彼 得 并 没 有 来 自 己 实 现 这 些 功 能 , 相 反 , 他 使 用 了<br />

event 关 键 字 让 C# 编 译 器 为 他 构 建 这 些 方 法 :<br />

class Worker {<br />

...<br />

public event WorkStarted started;<br />

public event WorkProgressing progressing;<br />

public event WorkCompleted completed;<br />

}<br />

彼 得 知 道 event 关 键 字 在 委 托 的 外 边 包 装 了 一 个 property, 仅 让 C# 客 户 通<br />

过 += 和 -= 操 作 符 来 添 加 和 移 除 , 强 迫 他 的 老 板 和 宇 宙 正 确 地 使 用 事 件 。<br />

static void Main() {<br />

Worker peter = new Worker();<br />

Boss boss = new Boss();<br />

peter.completed += new WorkCompleted(boss.WorkCompleted);<br />

peter.started += new WorkStarted(Universe.WorkerStartedWork);<br />

peter.completed += new WorkCompleted(Universe.WorkerCompletedWork);<br />

peter.DoWork();<br />

}<br />

Console.WriteLine(“Main: 工 人 工 作 完 成 ”);<br />

Console.ReadLine();


“ 收 获 ” 所 有 结 果<br />

到 这 时 , 彼 得 终 于 可 以 送 一 口 气 了 , 他 成 功 地 满 足 了 所 有 监 听 者 的 需 求 , 同<br />

时 避 免 了 与 特 定 实 现 的 紧 耦 合 。 但 是 他 注 意 到 他 的 老 板 和 宇 宙 都 为 它 的 工 作 打 了<br />

分 , 但 是 他 仅 仅 接 收 了 一 个 分 数 。 面 对 多 个 监 听 者 , 他 想 要 “ 收 获 ” 所 有 的 结 果 ,<br />

于 是 他 深 入 到 代 理 里 面 , 轮 询 监 听 者 列 表 , 手 工 一 个 个 调 用 :<br />

public void DoWork() {<br />

...<br />

Console.WriteLine("“ 工 作 : 工 作 完 成 ”");<br />

if( completed != null ) {<br />

foreach( WorkCompleted wc in completed.GetInvocationList() ) {<br />

int grade = wc();<br />

Console.WriteLine(“ 工 人 的 工 作 得 分 =” + grade);<br />

}<br />

}<br />

}<br />

异 步 通 知 : 激 发 & 忘 掉<br />

同 时 , 他 的 老 板 和 宇 宙 还 要 忙 于 处 理 其 他 事 情 , 也 就 是 说 他 们 给 彼 得 打 分 所<br />

花 费 的 事 件 变 得 非 常 长 :<br />

class Boss {<br />

public int WorkCompleted() {<br />

System.Threading.Thread.Sleep(3000);<br />

Console.WriteLine("Better..."); return 6; /* 总 分 为 10 */<br />

}<br />

}<br />

class Universe {<br />

static int WorkerCompletedWork() {<br />

System.Threading.Thread.Sleep(4000);<br />

Console.WriteLine("Universe is pleased with worker's work");<br />

return 7;<br />

}<br />

...<br />

}<br />

很 不 幸 , 彼 得 每 次 通 知 一 个 监 听 者 后 必 须 等 待 它 给 自 己 打 分 , 现 在 这 些 通 知<br />

花 费 了 他 太 多 的 工 作 事 件 。 于 是 他 决 定 忘 掉 分 数 , 仅 仅 异 步 激 发 事 件 :<br />

public void DoWork() {<br />

...<br />

Console.WriteLine("“ 工 作 : 工 作 完 成 ”");


}<br />

if( completed != null ) {<br />

foreach( WorkCompleted wc in completed.GetInvocationList() )<br />

{<br />

wc.BeginInvoke(null, null);<br />

}<br />

}<br />

异 步 通 知 : 轮 询<br />

这 使 得 彼 得 可 以 通 知 他 的 监 听 者 , 然 后 立 即 返 回 工 作 , 让 进 程 的 线 程 池 来 调<br />

用 这 些 代 理 。 随 着 时 间 的 过 去 , 彼 得 发 现 他 丢 失 了 他 工 作 的 反 馈 , 他 知 道 听 取 别<br />

人 的 赞 扬 和 努 力 工 作 一 样 重 要 , 于 是 他 异 步 激 发 事 件 , 但 是 周 期 性 地 轮 询 , 取 得<br />

可 用 的 分 数 。<br />

public void DoWork() {<br />

...<br />

Console.WriteLine("“ 工 作 : 工 作 完 成 ”");<br />

if( completed != null ) {<br />

foreach( WorkCompleted wc in completed.GetInvocationList() ) {<br />

IAsyncResult res = wc.BeginInvoke(null, null);<br />

while( !res.IsCompleted ) System.Threading.Thread.Sleep(1);<br />

int grade = wc.EndInvoke(res);<br />

Console.WriteLine(“ 工 人 的 工 作 得 分 =” + grade);<br />

}<br />

}<br />

}<br />

异 步 通 知 : 委 托<br />

不 幸 地 , 彼 得 有 回 到 了 一 开 始 就 想 避 免 的 情 况 中 来 , 比 如 , 老 板 站 在 背 后 盯<br />

着 他 工 作 。 于 是 , 他 决 定 使 用 自 己 的 委 托 作 为 他 调 用 的 异 步 委 托 完 成 的 通 知 , 让<br />

他 自 己 立 即 回 到 工 作 , 但 是 仍 可 以 在 别 人 给 他 的 工 作 打 分 后 得 到 通 知 :<br />

public void DoWork() {<br />

...<br />

Console.WriteLine("“ 工 作 : 工 作 完 成 ”");<br />

if( completed != null ) {<br />

foreach( WorkCompleted wc in completed.GetInvocationList() ) {<br />

wc.BeginInvoke(new AsyncCallback(WorkGraded), wc);<br />

}<br />

}<br />

}<br />

private void WorkGraded(IAsyncResult res) {


}<br />

WorkCompleted wc = (WorkCompleted)res.AsyncState;<br />

int grade = wc.EndInvoke(res);<br />

Console.WriteLine(“ 工 人 的 工 作 得 分 =” + grade);<br />

宇 宙 中 的 幸 福<br />

彼 得 、 他 的 老 板 和 宇 宙 最 终 都 满 足 了 。 彼 得 的 老 板 和 宇 宙 可 以 收 到 他 们 感 兴<br />

趣 的 事 件 通 知 , 减 少 了 实 现 的 负 担 和 非 必 需 的 往 返 “ 差 旅 费 ”。 彼 得 可 以 通 知 他<br />

们 , 而 不 管 他 们 要 花 多 长 时 间 来 从 目 的 方 法 中 返 回 , 同 时 又 可 以 异 步 地 得 到 他 的<br />

结 果 。 彼 得 知 道 , 这 并 不 * 十 分 * 简 单 , 因 为 当 他 异 步 激 发 事 件 时 , 方 法 要 在 另 外<br />

一 个 线 程 中 执 行 , 彼 得 的 目 的 方 法 完 成 的 通 知 也 是 一 样 的 道 理 。 但 是 ,62H 迈 克 和 63H 彼<br />

得 是 好 朋 友 , 他 很 熟 悉 线 程 的 事 情 , 可 以 在 这 个 领 域 提 供 指 导 。<br />

他 们 永 远 幸 福 地 生 活 下 去 ……< 完 ><br />

2005 年 9 月 2 日<br />

作 者 Blog:64Hhttp://blog.csdn.net/uoyevoli/


65H 衔 接 UI 线 程 和 管 理 后 台 工 作 线 程 的 类 ( 多 线 程 、 异 步 调 用 )<br />

一 、 引 言<br />

在 编 写 Windows form 时 , 如 果 直 接 在 UI 线 程 要 运 行 一 个 费 时 方 法 的 话 ( 如 从 数 据 库 查 询 大 量 数 据<br />

时 ), 会 引 起 程 序 “ 假 死 ”, 从 而 导 致 用 户 不 满 。 这 个 时 候 就 需 要 通 过 多 线 程 技 术 来 解 决 , 提 高 界 面 交<br />

互 性 能 , 方 便 用 户 使 用 。<br />

一 般 通 过 三 种 方 式 解 决 :<br />

1. 通 过 System.Threading.Thread 类 , 创 建 新 的 线 程 ,Thread.Start 运 行 费 时 方 法 。<br />

2. 通 过 System.Threading.ThreadPool 类 , 将 费 时 任 务 提 交 到 线 程 池 中 , 等 待 运 行 。<br />

以 上 两 种 方 法 , 基 本 思 路 是 在 UI 界 面 中 控 制 线 程 的 启 动 和 中 止 , 在 线 程 中 回 调 用 UI 界 面 方 法 , 更 新 界<br />

面 。 在 线 程 中 回 调 UI 界 面 方 法 时 , 特 别 是 涉 及 更 新 控 件 属 性 时 , 如 果 不 注 意 , 存 在 很 大 的 隐 患 。 这 两<br />

种 办 法 , 编 码 和 控 制 结 构 较 为 复 杂 , 需 要 启 动 和 管 理 额 外 的 线 程 占 用 资 源 。<br />

3. 通 过 异 步 委 托 调 用 , 将 该 方 法 排 队 到 系 统 线 程 池 的 线 程 中 运 行 , 而 在 费 时 方 法 中 也 通 过<br />

Control.BeginInvoke 异 步 回 调 , 达 到 " 启 动 后 不 管 " 的 目 的 。<br />

这 种 方 法 , 编 码 简 单 , 程 序 结 构 较 为 清 晰 , 充 分 利 用 .NET 框 架 的 异 步 委 托 功 能 , 但 要 对 异 步 调 用 知 识<br />

较 熟 悉 。<br />

66H 相 关 知 识 点 参 见<br />

现 利 用 .NET 异 步 委 托 调 用 功 能 , 编 写 Task 抽 象 类 , 以 方 便 管 理 后 台 工 作 线 程 , 衔 接 后 台 线 程 与 UI<br />

线 程 的 联 系 。 该 抽 象 类 提 供 了 调 用 和 管 理 的 框 架 , 没 有 方 法 的 实 现 细 节 , 通 过 继 承 类 、 重 写 方 法 , 可<br />

以 实 现 想 要 的 功 能 。 主 要 功 能 如 下 :<br />

1. 利 用 异 步 委 托 调 用 , 实 际 多 线 程 , 不 需 要 单 独 后 台 线 程 。<br />

2. 通 过 委 托 、 事 件 驱 动 , 实 际 后 台 与 前 台 UI 线 程 的 联 系 , 实 现 事 件 广 播 。<br />

3. 支 持 正 常 取 消 后 台 工 作 方 法 ( 费 时 方 法 ) 运 行 , 也 可 以 强 制 中 止 线 程 。<br />

4. 能 够 捕 获 取 消 、 强 制 中 止 和 方 法 出 错 三 种 情 况 , 并 突 发 相 关 事 件 , 以 便 进 行 释 放 资 源 等 操 作 。<br />

5. 通 过 异 步 调 用 , 在 工 作 方 法 中 安 全 调 用 涉 及 UI 控 件 的 方 法 。<br />

6. 自 行 管 理 工 作 进 程 状 态 , 提 供 状 态 变 化 事 件 。<br />

7. 只 要 工 作 方 法 调 用 签 名 , 符 合 定 义 的 TaskDelegate 委 托 接 口 , 可 通 过 StartTask(TaskDelegate<br />

worker ,params object[] args ) 方 便 调 用 。 在 实 际 使 用 时 , 可 在 继 承 类 中 定 义 多 个 相 同 调 用 接 口 的 方<br />

法 , 避 免 重 复 编 码 , 较 为 方 便 。


给 大 家 作 个 参 考 , 而 大 牛 呢 , 多 点 指 正 。 当 是 扔 个 砖 头 , 想 砸 块 玉 吧 。<br />

二 、 代 码<br />

1 using System;<br />

2 using System.Windows.Forms;<br />

3<br />

4 namespace Net66.AsynchThread<br />

5 {<br />

6 /// <br />

7 /// 任 务 工 作 状 态<br />

8 /// <br />

9 public enum TaskStatus<br />

10<br />

37<br />

38 /// <br />

39 /// 任 务 状 态 消 息<br />

40 /// <br />

41 public class TaskEventArgs : EventArgs<br />

42<br />

137<br />

138 /// <br />

139 /// 任 务 的 工 作 方 法 (Work) 的 委 托 接 口<br />

140 /// 传 入 值 : 对 象 数 组 (object[])<br />

141 /// 返 回 值 : 对 象 (object)<br />

142 /// <br />

143 public delegate object TaskDelegate( params object[] args );<br />

144<br />

145 ///


146 /// 任 务 事 件 的 委 托 接 口<br />

147 /// <br />

148 public delegate void TaskEventHandler( object sender, TaskEventArgs e );<br />

149<br />

150 abstract public class Task<br />

151 {<br />

152 内 部 属 性<br />

178<br />

179 事 件<br />

201<br />

202 属 性<br />

267<br />

268 触 发 事 件<br />

358<br />

359 工 作 进 程 管 理<br />

497<br />

498 工 作 方 法 的 基 础<br />

520 }<br />

521 }<br />

522<br />

523 使 用 Task 类 /*<br />

525<br />

526 使 用 Task 类<br />

527<br />

528 一 . 在 UI 线 程 中 创 建 Task 类<br />

529<br />

530 Task 类 负 责 管 理 后 台 线 程 。 要 使 用 Task 类 , 必 须 做 的 事 情 就 是 创 建 一 个 Task 对 象 , 注 册 它 激<br />

发 的 事 件 , 并 且 实 现 这 些 事 件 的 处 理 。 因 为 事 件 是 在 UI 线 程 上 激 发 的 , 所 以 您 根 本 不 必 担 心 代 码 中<br />

的 线 程 处 理 问 题 。<br />

531


532 下 面 的 示 例 展 示 了 如 何 创 建 Task 对 象 。 现 假 设 UI 有 两 个 按 钮 , 一 个 用 于 启 动 运 算 , 一 个 用 于<br />

停 止 运 算 , 还 有 一 个 进 度 栏 显 示 当 前 的 计 算 进 度 。<br />

533<br />

534 // 创 建 任 务 管 理 对 象<br />

535 _Task = new Task();<br />

536 // 挂 接 任 务 管 理 对 象 工 作 状 态 变 化 事 件<br />

537 _Task.TaskStatusChanged += new TaskEventHandler( OnTaskStatusChanged );<br />

538 // 挂 接 任 务 管 理 对 象 工 作 进 度 变 化 事 件<br />

539 _Task.TaskProgressChanged += new TaskEventHandler( OnTaskProgressChanged );<br />

540<br />

541 (1)<br />

542 用 于 计 算 状 态 和 计 算 进 度 事 件 的 事 件 处 理 程 序 相 应 地 更 新 UI, 例 如 通 过 更 新 状 态 栏 控 件 。<br />

543<br />

544 private void OnTaskProgressChanged( object sender,TaskEventArgs e )<br />

545 {<br />

546 _progressBar.Value = e.Progress;<br />

547 }<br />

548 (2)<br />

549 下 面 的 代 码 展 示 的 TaskStatusChanged 事 件 处 理 程 序 更 新 进 度 栏 的 值 以 反 映 当 前 的 计 算 进 度 。<br />

假 定 进 度 栏 的 最 小 值 和 最 大 值 已 经 初 始 化 。<br />

550<br />

551 private void OnTaskStatusChanged( object sender, TaskEventArgs e )<br />

552 {<br />

553 switch ( e.Status )<br />

554 {<br />

555 case TaskStatus.Running:<br />

556 button1.Enabled = false;<br />

557 button2.Enabled = true;<br />

558 break;<br />

559 case TaskStatus.Stop:


560 button1.Enabled = true;<br />

561 button2.Enabled = false;<br />

562 break;<br />

563 case TaskStatus.CancelPending:<br />

564 button1.Enabled = false;<br />

565 button2.Enabled = false;<br />

566 break;<br />

567 }<br />

568 }<br />

569<br />

570 在 这 个 示 例 中 ,TaskStatusChanged 事 件 处 理 程 序 根 据 计 算 状 态 启 用 和 禁 用 启 动 和 停 止 按 钮 。<br />

这 可 以 防 止 用 户 尝 试 启 动 一 个 已 经 在 进 行 的 计 算 , 并 且 向 用 户 提 供 有 关 计 算 状 态 的 反 馈 。<br />

571<br />

572 通 过 使 用 Task 对 象 中 的 公 共 方 法 ,UI 为 每 个 按 钮 单 击 实 现 了 窗 体 事 件 处 理 程 序 , 以 便 启 动 和<br />

停 止 计 算 。 例 如 , 启 动 按 钮 事 件 处 理 程 序 调 用 StartTask 方 法 , 如 下 所 示 。<br />

573<br />

574 private void startButton_Click( object sender, System.EventArgs e )<br />

575 {<br />

576 _Task.StartTask( new object[] {} );<br />

577 }<br />

578<br />

579 类 似 地 , 停 止 计 算 按 钮 通 过 调 用 StopTask 方 法 来 停 止 计 算 , 如 下 所 示 。<br />

580<br />

581 private void stopButton_Click( object sender, System.EventArgs e )<br />

582 {<br />

583 _Task.StopTask();<br />

584 }<br />

585<br />

586 二 . 可 能 在 非 UI 线 程 中 使 用 Task 类 时<br />

587 (1) 和 (2) 应 作 如 下 改 变


588<br />

589 (1)<br />

590 用 于 计 算 状 态 和 计 算 进 度 事 件 的 事 件 处 理 程 序 相 应 地 更 新 UI, 例 如 通 过 更 新 状 态 栏 控 件 。<br />

591<br />

592 private void OnTaskProgressChanged( object sender,TaskEventArgs e )<br />

593 {<br />

594 if (InvokeRequired ) // 不 在 UI 线 程 上 , 异 步 调 用<br />

595 {<br />

596 TaskEventHandler TPChanged = new TaskEventHandler( OnTaskProgressChanged );<br />

597 this.BeginInvoke(TPChanged,new object[] {sender,e});<br />

598 }<br />

599 else // 更 新<br />

600 {<br />

601 _progressBar.Value = e.Progress;<br />

602 }<br />

603 }<br />

604 (2)<br />

605 下 面 的 代 码 展 示 的 TaskStatusChanged 事 件 处 理 程 序 更 新 进 度 栏 的 值 以 反 映 当 前 的 计 算 进 度 。<br />

假 定 进 度 栏 的 最 小 值 和 最 大 值 已 经 初 始 化 。<br />

606<br />

607 private void OnTaskStatusChanged( object sender, TaskEventArgs e )<br />

608 {<br />

609 if (InvokeRequired ) // 不 在 UI 线 程 上 , 异 步 调 用<br />

610 {<br />

611 TaskEventHandler TSChanged = new TaskEventHandler( OnTaskStatusChanged );<br />

612 this.BeginInvoke(TSChanged,new object[] {sender,e});<br />

613 }<br />

614 else // 更 新<br />

615 {<br />

616 switch ( e.Status )


617 {<br />

618 case TaskStatus.Running:<br />

619 button1.Enabled = false;<br />

620 button2.Enabled = true;<br />

621 break;<br />

622 case TaskStatus.Stop:<br />

623 button1.Enabled = true;<br />

624 button2.Enabled = false;<br />

625 break;<br />

626 case TaskStatus.CancelPending:<br />

627 button1.Enabled = false;<br />

628 button2.Enabled = false;<br />

629 break;<br />

630 }<br />

631 }<br />

632 }<br />

633<br />

634 */<br />

636<br />

三 、 示 例<br />

1. 启 动 时 的 UI 界 面


2. 后 台 工 作 方 法 ( 费 用 方 法 ) 运 行 后 , 任 务 状 态 为 Running<br />

3. 强 制 中 止 工 作 方 法 , 运 行 任 务 状 态 Aborted


4. 工 作 方 法 突 发 错 误 时 , 任 务 状 态 ThrowErrorStoped<br />

5. 工 作 方 法 正 常 结 束 或 正 常 取 消 而 结 束 时 , 任 务 状 态 Stopped<br />

67H 示 例 代 码 下 载<br />

posted on 2005-08-03 00:19 68HNet66 阅 读 (4810) 69H<br />

评 论 (25) 70H<br />

编 辑 71H<br />

收 藏 所 属 分 类 : 72HC#<br />

评 论<br />

73H#4 楼 2005-08-03 08:46 Nineteen@newsmth [ 未 注 册 用 户 ]<br />

System.Threading.ThreadPool 还 是 不 要 用 的 好 , 这 东 西 牵 扯 进 程 的 异 步 调 用 , 这 东 西 微 软 就 该 设 置 成 internal<br />

74H 回 复 75H<br />

引 用 76H<br />

查 看


77H#5 楼 2005-08-03 08:49 学 习 [ 未 注 册 用 户 ]<br />

78Hhttp://www.vckbase.com/document/viewdoc/?id=1126<br />

79H 回 复 80H<br />

引 用 81H<br />

查 看<br />

82H#6 楼 2005-08-03 08:54 83HJames<br />

这 些 BeginXXXX 的 方 法 都 是 使 用 了 ThreadPool 的 。<br />

84H 回 复 85H<br />

引 用 86H<br />

查 看<br />

87H#7 楼 2005-08-03 09:06 88H 张 老 三 [ 未 注 册 用 户 ]<br />

刚 完 成 了 一 个 图 库 管 理 系 统 , 使 用 的 就 是 这 种 方 式 . 感 觉 还 不 错 .<br />

89H 回 复 90H<br />

引 用 91H<br />

查 看<br />

92H#8 楼 2005-08-03 09:08 win [ 未 注 册 用 户 ]<br />

文 章 的 意 思 应 该 是 不 用 显 式 使 用 ThreadPool, 通 过 系 统 默 认 调 用 吧<br />

93H 回 复 94H<br />

引 用 95H<br />

查 看<br />

96H#10 楼 2005-08-03 09:39 97H<br />

妖 居<br />

异 步 委 托 也 是 刚 刚 用 过 , 做 一 个 分 析 目 录 的 工 具 的 时 候 用 的 。<br />

98H 回 复 99H<br />

引 用 100H<br />

查 看<br />

101H#11 楼 2005-08-03 09:41 102HJames<br />

我 的 意 思 是 说 , 这 些 异 步 委 托 调 用 什 么 的 , 其 内 部 都 是 使 用 了 ThreadPool 的 , 这 样 当 然 不 用 你 自 己 去 直 接 使 用<br />

ThreadPool 了 。<br />

103H 回 复 104H<br />

引 用 105H<br />

查 看<br />

106H#12 楼 2005-08-03 18:48 107Hyinh<br />

最 近 写 的 项 目 中 因 为 用 得 很 多 网 络 交 互 , 频 繁 的 异 步 调 用 , 但 对 控 制 这 些 线 程 却 没 有 一 点 主 意 , 希 望 能 从 你 的 文<br />

章 中 得 到 一 些 启 示 。<br />

108H 回 复 109H<br />

引 用 110H<br />

查 看<br />

111H#14 楼 2005-09-14 16:10 112H 双 鱼 座<br />

看 了 下 楼 主 的 代 码 , 大 致 上 不 错 。 不 过 在 封 装 性 方 面 做 得 不 够 ... 其 实 封 装 性 是 需 要 早 一 些 构 思 的 , 比 功 能 的 实 现<br />

还 要 再 早 一 些 。<br />

先 搞 清 楚 你 的 Task 的 派 生 类 依 赖 什 么 , 也 就 是 说 可 供 派 生 类 调 用 的 方 法 , 例 如 Fire*** 方 法 ; 接 下 来 搞 清 楚 你 的<br />

Task 的 派 生 类 不 依 赖 什 么 , 例 如 必 须 在 重 写 方 法 中 调 用 base.Work 这 样 的 问 题 ; 最 后 是 参 数 的 传 递 问 题 。 由 此 我<br />

想 到 了 在 Delphi3 中 对 Thread 的 封 装 , 有 一 个 同 步 执 行 方 法 的 方 法 , 在 调 用 者 不 知 道 任 何 细 节 的 情 况 下 可 以 进<br />

行 安 全 的 调 用 。 当 然 , 那 个 封 装 代 价 有 点 大 了 。<br />

我 作 了 如 下 修 改 :<br />

1. 定 义 一 个 抽 象 方 法 :<br />

protected abstract object Execute(params object [] args);<br />

这 个 方 法 才 是 真 正 需 要 继 承 的 方 法 。<br />

2. 基 类 的 Work 方 法 改 成 私 有 方 法 (Work 这 个 名 字 取 得 不 是 太 好 ), 代 码 这 样 写 :


System.Threading.Thread.CurrentThread.IsBackground = true;<br />

_workThread = System.Threading.Thread.CurrentThread;<br />

return Execute(args);<br />

3. 加 一 个 线 程 方 法 :<br />

void start()<br />

{<br />

StartTask(new TaskDelegate(Work), tempArgs);<br />

}<br />

4. 定 义 一 个 私 有 字 段 用 来 向 线 程 传 递 参 数 :<br />

private object[] tempArgs;<br />

5. 最 后 加 一 个 启 动 方 法 :<br />

public void Start(params object[] args)<br />

{<br />

tempArgs = args;<br />

Thread thread = new Thread(new ThreadStart(start));<br />

thread.Start();<br />

}<br />

改 造 完 成 了 。 客 户 端 代 码 由 此 变 得 非 常 简 洁 :<br />

1. 当 按 下 “ 开 始 执 行 ” 时 的 代 码 :<br />

_Task.Start(new object[] {});<br />

2.Concrete 类 型 newasynchui 的 代 码 也 简 单 了 , 只 需 要 重 写 一 个 Execute 方 法 而 不 是 重 载 一 个 Work 和 另 外 写 一<br />

个 Work2, 在 方 法 中 也 不 必 调 用 base.Work, 你 想 调 用 也 调 用 不 了 , 因 为 是 那 是 私 有 方 法 。<br />

当 然 还 有 其 它 一 些 更 好 的 建 议 , 例 如 不 必 定 义 那 么 多 的 事 件 , 其 实 一 两 个 就 足 够 了 , 也 不 需 要 派 生 新 的 EventArgs<br />

类 型 , 因 为 从 sender 中 可 以 获 得 所 有 的 信 息 。<br />

我 的 解 决 方 案 可 能 更 邪 一 点 了 , 因 为 我 是 用 反 射 和 Emit 实 现 的 , 原 理 和 你 的 差 不 多 , 只 不 过 客 户 端 代 码 可 以 非<br />

常 简 单 而 已 。<br />

113H 回 复 114H<br />

引 用 115H<br />

查 看<br />

116H#20 楼 2006-10-07 16:37 可 乐 [ 匿 名 ] [ 未 注 册 用 户 ]<br />

看 了 例 程 非 常 激 动 , 也 许 是 还 有 很 多 东 西 要 学 没 有 看 的 很 彻 底 , 希 望 楼 主 能 解 释 , 就 是 在 UI 中 启 动 一 个 辅 助 线<br />

程 后 往 往 会 应 为 需 要 在 UI 启 动 的 这 个 辅 助 线 程 中 在 启 动 其 他 线 程 , 这 个 时 候 该 怎 样 使 用 Task 抽 象 类 , 该 如 何 将<br />

辅 助 线 程 启 动 的 其 他 线 程 中 的 信 息 显 示 在 UI 中 , 还 有 怎 么 才 能 利 用 AsynchThread 在 UI 中 停 止 辅 助 线 程 的 时 候<br />

能 同 时 停 止 辅 助 线 程 启 动 的 其 他 线 程 , 希 望 楼 主 能 解 答 , 谢 谢 !!<br />

117H 回 复 118H<br />

引 用 119H<br />

查 看


在 .NET 客 户 端 程 序 中 使 用 多 线 程<br />

admin 发 表 于 :04-06-08<br />

通 常 认 为 在 编 写 程 序 中 用 到 多 线 程 是 一 个 高 级 的 编 程 任 务 , 容 易 发 生 错 误 。 在 本 月 的 栏 目 中 , 我 将 在 一 个 Windows 窗 体 应 用 程 序 中 使 用 多 线 程 ,<br />

它 具 有 实 际 的 意 义 , 同 时 尽 量 使 事 情 简 单 。 我 的 目 标 是 在 一 个 普 通 的 需 求 描 述 中 用 最 好 的 办 法 讲 解 多 线 程 ; 客 户 仍 然 比 较 喜 欢 使 用 户 交 互 方 式 的 应<br />

用 程 序 。<br />

多 线 程 通 常 和 服 务 器 端 软 件 , 可 扩 展 性 及 性 能 技 术 联 系 在 一 起 。 然 而 , 在 微 软 .NET 框 架 中 , 许 多 服 务 器 端 应 用 程 序 都 驻 留 在 ASP.NET 体 系 结<br />

构 中 。 同 样 , 这 些 应 用 程 序 在 逻 辑 上 是 单 线 程 的 , 因 为 IIS 和 ASP.NET 在 ASP.NET Web Form 或 Web 服 务 程 序 中 执 行 了 许 多 或 所 有 的 多 线 程 。 在<br />

ASP.NET 应 用 程 序 中 你 一 般 可 以 忽 略 线 程 性 。 这 就 是 为 什 么 在 .NET 框 架 中 , 多 线 程 更 倾 向 于 在 客 户 端 使 用 的 一 个 原 因 , 比 如 在 保 证 同 用 户 交 互 的 同<br />

时 而 执 行 一 个 很 长 的 操 作 。<br />

线 程 背 景<br />

线 程 执 行 代 码 。 它 们 由 操 作 系 统 实 现 , 是 CPU 本 身 的 一 种 抽 象 。 许 多 系 统 都 只 有 一 个 CPU, 线 程 是 把 CPU 快 速 的 处 理 能 力 分 开 而 执 行 多 个 操 作<br />

的 一 种 方 法 , 使 它 们 看 起 来 好 像 同 步 似 的 。 即 使 一 个 系 统 由 多 个 CPU, 但 运 行 的 线 程 一 般 要 比 处 理 器 多 。<br />

在 一 个 Windows 为 基 础 的 应 用 程 序 中 , 每 一 个 进 程 至 少 要 有 一 个 线 程 , 它 能 够 执 行 机 器 语 言 指 令 。 一 旦 一 个 进 程 的 所 有 线 程 都 中 止 了 , 进 程 本<br />

身 和 它 所 占 用 的 资 源 将 会 被 Windows 清 除 。<br />

许 多 应 用 程 序 都 被 设 计 为 单 线 程 程 序 , 这 意 味 着 该 程 序 实 现 的 进 程 从 来 不 会 有 超 过 一 个 线 程 在 执 行 , 即 使 在 系 统 中 有 多 个 同 样 的 处 理 在 进 行 。<br />

一 般 一 个 进 程 不 会 关 心 系 统 中 其 他 进 程 的 线 程 的 执 行 。<br />

然 而 , 在 单 个 进 程 里 的 所 有 线 程 不 仅 共 享 虚 拟 地 址 空 间 , 而 且 许 多 进 程 级 的 资 源 也 被 共 享 , 比 如 文 件 和 窗 口 句 柄 等 。 由 于 进 程 资 源 共 享 的 特 征 ,<br />

一 个 线 程 必 须 考 虑 同 一 进 程 中 其 它 线 程 正 在 做 什 么 。 线 程 同 步 是 在 多 线 程 的 进 程 中 保 持 各 线 程 互 不 冲 突 的 一 门 艺 术 。 这 也 使 得 多 线 程 比 较 困 难 。<br />

最 好 的 方 式 是 只 有 在 需 要 时 才 使 用 多 线 程 , 尽 量 保 持 事 情 简 单 。 而 且 要 避 免 线 程 同 步 的 情 况 。 在 本 栏 目 中 , 我 将 向 你 展 示 如 何 为 一 个 普 通 的 客<br />

户 应 用 程 序 做 这 些 事 情 。<br />

为 什 么 使 用 多 个 线 程 ?<br />

已 经 有 许 多 单 线 程 的 客 户 端 应 用 程 序 , 而 且 每 天 还 有 许 多 正 在 被 写 。 在 许 多 情 况 下 , 单 线 程 的 行 为 已 经 足 够 了 。<br />

然 而 , 在 某 些 特 定 的 应 用 程 序 中 加 入 一 些 异 步 行 为 可 以 提 高 你 的 经 验 。 典 型 的 数 据 库 前 端 程 序 是 一 个 很 好 的 例 子 。<br />

数 据 库 查 询 需 要 花 费 大 量 时 间 完 成 。 在 一 个 单 线 程 的 应 用 程 序 里 , 这 些 查 询 会 导 致 window 消 息 处 理 能 力 阻 塞 , 导 致 程 序 的 用 户 交 互 被 冻 结 。 解<br />

决 办 法 就 是 , 这 个 我 将 要 详 细 描 述 , 用 一 个 线 程 处 理 来 自 操 作 系 统 的 消 息 , 而 另 外 一 个 线 程 做 一 个 很 长 的 工 作 。 在 你 的 代 码 中 使 用 第 二 个 线 程 的 重


要 原 因 就 是 即 使 在 幕 后 有 一 个 繁 忙 的 工 作 在 进 行 , 也 要 保 证 你 的 程 序 的 用 户 交 互 有 响 应 。<br />

我 们 首 先 看 一 下 执 行 一 长 串 操 作 的 单 线 程 的 GUI 程 序 。 然 后 我 们 将 用 额 外 的 线 程 整 理 该 程 序 。<br />

Figure 1 是 用 C# 写 的 一 个 程 序 的 完 整 源 代 码 。 它 创 建 了 一 个 带 有 文 本 框 和 按 钮 的 窗 体 。 如 果 你 在 文 本 框 中 键 入 了 一 个 数 字 , 然 后 按 下 按 钮 ,<br />

这 个 程 序 将 处 理 你 输 入 的 那 个 数 字 , 它 表 示 秒 数 , 每 秒 钟 响 铃 一 次 代 表 后 台 的 处 理 。 除 了 Figure 1 的 代 码 外 , 你 可 以 从 本 文 开 头 的 链 接 中 下 载 完<br />

整 的 代 码 。 下 载 或 键 入 Figure 1 所 示 的 代 码 , 在 读 之 前 编 译 运 行 它 ,( 编 译 前 , 在 Visual Studio.NET 中 右 击 你 的 工 程 , 加 入 Microsoft Visual Basic<br />

运 行 时 引 用 ) 当 你 试 着 运 行 Figure 1 中 的<br />

SingleThreadedForm.cs 应 用 程 序 时 , 你 马 上 就 会 看 到 几 个 问 题 。<br />

Figure 1 SingleThreadedForm.cs<br />

using System;<br />

using System.Drawing;<br />

using System.Threading;<br />

using System.Windows.Forms;<br />

using Microsoft.VisualBasic;<br />

class App {<br />

// Application entry point<br />

public static void Main() {<br />

}<br />

}<br />

// Run a Windows Forms message loop<br />

Application.Run(new SingleThreadedForm());<br />

// A Form-derived type<br />

class SingleThreadedForm : Form {<br />

// Constructor method<br />

public SingleThreadedForm() {<br />

// Create a text box<br />

text.Location = new Point(10, 10);<br />

text.Size = new Size(50, 20);<br />

Controls.Add(text);<br />

// Create a button<br />

button.Text = "Beep";<br />

button.Size = new Size(50, 20);<br />

button.Location = new Point(80, 10);<br />

// Register Click event handler


utton.Click += new EventHandler(OnClick);<br />

}<br />

Controls.Add(button);<br />

// Method called by the button's Click event<br />

void OnClick(Object sender, EventArgs args) {<br />

// Get an int from a string<br />

Int32 count = 0;<br />

try { count = Int32.Parse(text.Text); } catch (FormatException) {}<br />

}<br />

// Count to that number<br />

Count(count);<br />

// Method beeps once per second<br />

void Count(Int32 seconds) {<br />

}<br />

for (Int32 index = 0; index < seconds; index++) {<br />

Interaction.Beep();<br />

Thread.Sleep(1000);<br />

}<br />

}<br />

// Some private fields by which to reference controls<br />

Button button = new Button();<br />

TextBox text = new TextBox();<br />

在 你 第 一 次 测 试 运 行 时 , 在 文 本 框 中 输 入 20, 按 下 按 钮 。 你 将 看 到 程 序 的 用 户 交 互 变 得 完 全 没 有 响 应 了 。 你 不 能 单 击 按 钮 或 者 编 辑 文 本 框 , 程<br />

序 也 不 能 被 从 容 的 关 闭 , 如 果 你 覆 盖 该 窗 体 接 着 会 显 示 一 个 窗 口 的 部 分 区 域 , 它 将 不 再 重 绘 自 己 ( 见 Figure 2), 这 个 程 序 被 锁 定 足 足 20 秒 , 然<br />

而 它 还 可 以 继 续 响 铃 , 证 明 它 还 没 有 真 正 的 死 掉 。 这 个 简 单 的 程 序 解 释 了 单 线 程 GUI 程 序 的 问 题 。<br />

我 将 用 多 线 程 解 决 第 一 个 问 题 : 未 响 应 的 用 户 交 互 , 但 首 先 我 将 解 释 是 什 么 导 致 了 这 种 现 象 。<br />

线 程 和 Windows 用 户 界 面<br />

Windows Forms 类 库 建 立 在 大 家 所 熟 知 的 User32 Win32 API 基 础 上 。User32 实 现 了 GUI 的 基 本 元 素 , 例 如 窗 体 , 菜 单 及 按 钮 之 类 等 。 所 有 由<br />

User32 实 现 的 窗 体 和 控 件 都 使 用 了 事 件 驱 动 型 结 构 。<br />

这 里 简 单 的 讲 讲 它 们 如 何 工 作 。 发 生 在 窗 体 上 的 事 情 , 例 如 鼠 标 单 击 , 坐 标 变 化 , 大 小 变 化 和 重 绘 请 求 , 都 称 作 事 件 。 在 User32 API 模 型 中 的<br />

事 件 是 由 窗 体 消 息 表 示 的 。 每 一 个 窗 体 有 一 个 函 数 , 叫 做 窗 口 过 程 或 WndProc, 它 由 应 用 程 序 实 现 。WndProc 为 窗 体 负 责 处 理 窗 体 消 息 。


但 是 WndProc 不 是 神 奇 的 被 系 统 调 用 。 相 反 , 应 用 程 序 必 须 调 用 GetMessage 主 动 地 从 系 统 中 得 到 窗 体 消 息 。 该 消 息 被 应 用 程 序 调 用<br />

DispatchMethod API 方 法 分 配 到 它 们 的 目 标 窗 体 的 WndProc 方 法 中 。 应 用 程 序 只 是 简 单 的 循 环 接 收 和 分 配 窗 口 消 息 , 一 般 叫 做 消 息 泵 或 消 息 循 环 。<br />

线 程 拥 有 所 有 窗 体 , 这 样 它 就 可 以 提 取 消 息 ,WndProc 函 数 也 被 同 样 的 线 程 所 调 用 。<br />

现 在 回 到 Windows Forms 类 来 。Windows Forms 在 应 用 程 序 中 对 User32 的 消 息 结 构 进 行 了 大 约 95% 的 抽 象 。 代 替 了 WndProc 函 数 ,Windows Forms<br />

程 序 定 义 了 事 件 处 理 器 和 虚 拟 函 数 重 载 来 处 理 与 窗 体 ( 窗 口 ) 或 控 件 有 关 的 不 同 系 统 事 件 。 然 而 消 息 提 取 必 须 要 运 行 , 它 在 Windows Forms API 的<br />

Application.Run 方 法 里 面 实 现 。<br />

Figure 1 所 示 的 代 码 似 乎 仅 仅 调 用 了 Application.Run 接 着 就 退 出 了 。 然 而 这 缺 少 了 透 明 性 : 应 用 程 序 的 主 线 程 在 其 生 命 周 期 里 只 对<br />

Application.Run 进 行 一 次 调 用 进 行 消 息 提 取 , 其 结 果 却 为 用 应 用 程 序 其 它 部 分 创 造 了 不 同 事 件 处 理 器 的 调 用 。 当 窗 体 上 的 按 钮 被 单 击 时 , 在 Figure<br />

1 中 的 OnClick 方 法 被 主 线 程 调 用 , 该 线 程 同 样 要 负 责 在 Application.Run 中 提 取 消 息 。<br />

这 解 释 了 为 什 么 在 一 个 长 操 作 发 生 时 , 用 户 交 互 没 有 响 应 。 如 果 在 一 个 事 件 处 理 器 中 一 个 很 长 的 操 作 ( 如 数 据 库 查 询 ) 发 生 了 , 那 么 主 线 程 就<br />

被 占 用 , 它 又 需 要 不 断 提 取 消 息 。 没 有 能 力 提 取 消 息 并 发 送 到 窗 口 或 窗 体 上 , 就 没 有 能 力 响 应 调 整 大 小 , 重 绘 自 己 , 处 理 单 击 或 响 应 用 户 的 任 何 交<br />

互 。<br />

在 接 下 来 的 部 分 为 了 执 行 长 操 作 我 将 使 用 公 共 语 言 运 行 时 的 线 程 池 来 修 改 Figure 1 所 示 的 例 子 代 码 , 这 样 主 线 程 仍 然 可 以 提 取 消 息 。<br />

托 管 线 程 池<br />

CLR 为 每 一 个 托 管 进 程 维 护 了 一 个 线 程 池 , 这 意 味 着 当 你 的 应 用 程 序 主 线 程 需 要 进 行 某 些 异 步 处 理 时 , 你 可 以 很 容 易 的 从 线 程 池 中 借 助 某 个 线<br />

程 实 现 特 定 的 处 理 。 一 旦 处 理 工 作 完 成 , 线 程 被 归 还 到 线 程 池 以 便 以 后 使 用 。 让 我 们 看 一 个 例 子 , 修 改 使 用 线 程 池 。<br />

注 意 Figure 3 中 FlawMultiThreadForm.cs 中 红 色 部 分 表 示 的 行 ; 它 们 是 由 Figure 1 中 的 单 线 程 变 为 多 线 程 程 序 时 唯 一 要 修 改 的 代 码 。 如 果<br />

你 编 译 Figure 3 所 示 的 代 码 , 并 设 置 运 行 20 秒 , 你 将 看 到 当 处 理 20 个 响 铃 的 请 求 时 , 仍 然 能 够 响 应 用 户 的 交 互 。 在 客 户 端 程 序 中 使 用 多 线 程 来<br />

响 应 用 户 交 互 是 一 个 吸 引 人 的 原 因 。<br />

Figure 3 FlawedMultiThreadedForm.cs<br />

using System;<br />

using System.Drawing;<br />

using System.Threading;<br />

using System.Windows.Forms;<br />

using Microsoft.VisualBasic;<br />

class App {<br />

// Application entry point<br />

public static void Main() {<br />

// Run a Windows Forms message loop


Application.Run(new FlawedMultiThreadedForm());<br />

}<br />

}<br />

// A Form-derived type<br />

class FlawedMultiThreadedForm : Form {<br />

// Constructor method<br />

public FlawedMultiThreadedForm() {<br />

// Create a text box<br />

text.Location = new Point(10, 10);<br />

text.Size = new Size(50, 20);<br />

Controls.Add(text);<br />

// Create a button<br />

button.Text = "Beep";<br />

button.Size = new Size(50, 20);<br />

button.Location = new Point(80, 10);<br />

// Register Click event handler<br />

button.Click += new EventHandler(OnClick);<br />

}<br />

Controls.Add(button);<br />

// Method called by the button's Click event<br />

void OnClick(Object sender, EventArgs args) {<br />

// Get an int from a string<br />

Int32 count = 0;<br />

try { count = Int32.Parse(text.Text); } catch (FormatException) {}<br />

}<br />

// Count to that number<br />

WaitCallback async = new WaitCallback(Count);<br />

ThreadPool.QueueUserWorkItem(async, count);<br />

// Async method beeps once per second<br />

void Count(Object param) {<br />

Int32 seconds = (Int32) param;<br />

for (Int32 index = 0; index < seconds; index++) {<br />

Interaction.Beep();<br />

Thread.Sleep(1000);


}<br />

}<br />

}<br />

// Some private fields by which to reference controls<br />

Button button = new Button();<br />

TextBox text = new TextBox();<br />

然 而 , 在 Figure 3 中 所 做 的 变 化 , 却 引 入 了 一 个 新 问 题 ( 如 Figure 3 的 名 字 一 样 ); 现 在 用 户 可 以 启 动 多 个 同 时 响 铃 的 长 操 作 。 在 许 多 实<br />

时 应 用 中 这 会 导 致 线 程 间 的 冲 突 。 为 了 修 正 这 个 线 程 同 步 请 求 , 我 将 讲 述 这 些 , 但 首 先 熟 悉 一 下 CLR''''s 线 程 池 。<br />

类 库 中 的 System.Threading.ThreadPool 类 提 供 了 一 个 访 问 CLR''''s 线 程 池 的 API 接 口 , ThreadPool 类 型 不 能 被 实 例 化 , 它 由 静 态 成 员 组 成 。<br />

ThreadPool 类 型 最 重 要 的 方 法 是 对 ThreadPool.QueueUserWorkItem 的 两 个 重 载 。 这 两 种 方 法 让 你 定 义 一 个 你 愿 意 被 线 程 池 中 的 一 个 线 程 进 行 回 调<br />

的 函 数 。 通 过 使 用 类 库 中 的 WaitCallback 委 托 类 型 的 一 个 实 例 来 定 义 你 的 方 法 。 一 种 重 载 让 你 对 异 步 方 法 定 义 一 个 参 数 ; 这 是 Figure 3 所 使 用 的<br />

版 本 。<br />

下 面 的 两 行 代 码 创 建 一 个 委 托 实 例 , 代 表 了 一 个 Count 方 法 , 接 下 来 的 调 用 排 队 等 候 让 线 程 池 中 的 方 法 进 行 回 调 。<br />

WaitCallback async = new WaitCallback(Count);<br />

ThreadPool.QueueUserWorkItem(async, count);<br />

ThreadPool.QueueUserWorkItem 的 两 个 方 法 让 你 在 队 列 中 定 义 一 个 异 步 回 调 方 法 , 然 后 立 即 返 回 。 同 时 线 程 池 监 视 这 个 队 列 , 接 着 出 列 方 法 ,<br />

并 使 用 线 程 池 中 的 一 个 或 多 个 线 程 调 用 该 方 法 。 这 是 CLR''''s 线 程 池 的 主 要 用 法 。<br />

CLR''''s 线 程 池 也 被 系 统 的 其 它 APIs 所 使 用 。 例 如 , System.Threading.Timer 对 象 在 定 时 间 隔 到 来 时 将 会 在 线 程 池 中 排 队 等 候 回 调 。<br />

ThreadPool.RegisterWaitForSingleObject 方 法 当 响 应 内 核 系 统 同 步 对 象 有 信 号 时 会 在 线 程 池 中 排 队 等 候 调 用 。 最 后 , 回 调 由 类 库 中 的 不 同 异 步 方<br />

法 执 行 , 这 些 异 步 方 法 又 由 CLR''''s 线 程 池 来 执 行 。<br />

一 般 来 说 , 一 个 应 用 程 序 仅 仅 对 于 简 单 的 异 步 操 作 需 要 使 用 多 线 程 时 毫 无 疑 问 应 该 使 用 线 程 池 。 相 比 较 手 工 创 建 一 个 线 程 对 象 , 这 种 方 法 是 被<br />

推 荐 的 。 调 用 ThreadPool.QueueUserWorkItem 执 行 简 单 , 而 且 相 对 于 重 复 的 手 动 创 建 线 程 来 说 能 够 更 好 的 利 用 系 统 资 源 。<br />

最 简 单 的 线 程 同 步<br />

在 本 栏 目 开 始 我 就 称 保 持 线 程 同 步 而 不 互 相 冲 突 是 一 门 艺 术 。Figure 3 所 示 的 FlawedMultiThreadForm.cs 应 用 程 序 有 一 个 问 题 : 用 户 可 以 通<br />

过 单 击 按 钮 引 发 一 个 很 长 的 响 铃 操 作 , 他 们 可 以 继 续 单 击 按 钮 而 引 发 更 多 的 响 铃 操 作 。 如 果 不 是 响 铃 , 该 长 操 作 是 数 据 库 查 询 或 者 在 进 程 的 内 存 中<br />

进 行 数 据 结 构 操 作 , 你 一 定 不 想 在 同 一 时 间 内 , 有 一 个 以 上 的 线 程 做 同 样 的 工 作 。 最 好 的 情 况 下 这 是 系 统 资 源 的 一 种 浪 费 , 最 坏 的 情 况 下 会 导 致 数<br />

据 毁 灭 。<br />

最 容 易 的 解 决 办 法 就 是 禁 止 按 钮 一 类 的 用 户 交 互 元 素 ; 两 个 进 程 间 的 通 信 稍 微 有 点 难 度 。 过 一 会 我 将 给 你 看 如 何 做 这 些 事 情 。 但 首 先 , 让 我 指<br />

出 所 有 线 程 同 步 使 用 的 一 些 线 程 间 通 信 的 形 式 - 从 一 个 线 程 到 另 一 个 线 程 通 信 的 一 种 手 段 。 稍 后 我 将 讨 论 大 家 所 熟 知 的 AutoResetEvent 对 象 类 型 ,<br />

它 仅 用 在 线 程 间 通 信 。


现 在 让 我 们 首 先 看 一 下 为 Figure 3 中 FlawedMultiThreadedForm.cs 程 序 中 加 入 的 线 程 同 步 代 码 。 再 一 次 的 ,Figure 4<br />

CorrectMultiThreadedForm.cs 程 序 中 红 色 部 分 表 示 的 是 其 先 前 程 序 的 较 小 的 改 动 部 分 。 如 果 你 运 行 这 个 程 序 你 将 看 到 当 一 个 长 响 铃 操 作 在 进 行 时<br />

用 户 交 互 被 禁 止 了 ( 但 没 有 挂 起 ), 响 铃 完 成 的 时 候 又 被 允 许 了 。 这 次 这 些 代 码 的 变 化 已 经 足 够 了 , 我 将 逐 个 运 行 他 们 。<br />

Figure 4 CorrectMultiThreadedForm.cs<br />

using System;<br />

using System.Drawing;<br />

using System.Threading;<br />

using System.Windows.Forms;<br />

using Microsoft.VisualBasic;<br />

class App {<br />

// Application entry point<br />

public static void Main() {<br />

}<br />

}<br />

// Run a Windows Forms message loop<br />

Application.Run(new CorrectMultiThreadedForm());<br />

// A Form-derived type<br />

class CorrectMultiThreadedForm : Form{<br />

// Constructor method<br />

public CorrectMultiThreadedForm() {<br />

// Create a textbox<br />

text.Location = new Point(10, 10);<br />

text.Size = new Size(50, 20);<br />

Controls.Add(text);<br />

// Create a button<br />

button.Text = "Beep";<br />

button.Size = new Size(50, 20);<br />

button.Location = new Point(80, 10);<br />

// Register Click event handler<br />

button.Click += new EventHandler(OnClick);<br />

Controls.Add(button);<br />

// Cache a delegate for repeated reuse


enableControls = new BooleanCallback(EnableControls);<br />

}<br />

// Method called by the button's Click event<br />

void OnClick(Object sender, EventArgs args) {<br />

// Get an int from a string<br />

Int32 count = 0;<br />

try { count = Int32.Parse(text.Text); } catch (FormatException) {}<br />

}<br />

// Count to that number<br />

EnableControls(false);<br />

WaitCallback async = new WaitCallback(Count);<br />

ThreadPool.QueueUserWorkItem(async, count);<br />

// Async method beeps once per second<br />

void Count(Object param) {<br />

Int32 seconds = (Int32) param;<br />

for (Int32 index = 0; index < seconds; index++) {<br />

Interaction.Beep();<br />

Thread.Sleep(1000);<br />

}<br />

}<br />

Invoke(enableControls, new Object[]{true});<br />

void EnableControls(Boolean enable) {<br />

button.Enabled = enable;<br />

text.Enabled = enable;<br />

}<br />

// A delegate type and matching field<br />

delegate void BooleanCallback(Boolean enable);<br />

BooleanCallback enableControls;<br />

}<br />

// Some private fields by which to reference controls<br />

Button button = new Button();<br />

TextBox text = new TextBox();<br />

在 Figure 4 的 末 尾 处 有 一 个 EnableControls 的 新 方 法 , 它 允 许 或 禁 止 窗 体 上 的 文 本 框 和 按 钮 控 件 。 在 Figure 4 的 开 始 我 加 入 了 一 个<br />

EnableControls 调 用 , 在 后 台 响 铃 操 作 排 队 等 候 之 前 立 即 禁 止 文 本 框 和 按 钮 。 到 这 里 线 程 的 同 步 工 作 已 经 完 成 了 一 半 , 因 为 禁 止 了 用 户 交 互 , 所 以<br />

用 户 不 能 引 发 更 多 的 后 台 冲 突 操 作 。 在 Figure 4 的 末 尾 你 将 看 到 一 个 名 为 BooleanCallback 的 委 托 类 型 被 定 义 , 其 签 名 是 同 EnableControls 方 法


兼 容 的 。 在 那 个 定 义 之 前 , 一 个 名 为 EnableControls 的 委 托 域 被 定 义 ( 见 例 子 ), 它 引 用 了 该 窗 体 的 EnableControls 方 法 。 这 个 委 托 域 在 代 码 的<br />

开 始 处 被 分 配 。<br />

你 也 将 看 到 一 个 来 自 主 线 程 的 回 调 , 该 主 线 程 为 窗 体 和 其 控 件 拥 有 和 提 取 消 息 。 这 个 调 用 通 过 向 EnableControls 传 递 一 个 true 参 数 来 使 能 控<br />

件 。 这 通 过 后 台 线 程 调 用 窗 体 的 Invoke 方 法 来 完 成 , 当 其 一 旦 完 成 其 长 响 铃 操 时 。 代 码 传 送 的 委 托 引 用 EnableControls 去 Invoke, 该 方 法 的 参 数<br />

带 有 一 个 对 象 数 组 。Invoke 方 法 是 线 程 间 通 信 的 一 个 非 常 灵 活 的 方 式 , 特 别 是 对 于 Windows Forms 类 库 中 的 窗 口 或 窗 体 。 在 这 个 例 子 中 ,Invoke<br />

被 用 来 告 诉 主 GUI 线 程 通 过 调 用 EnableControls 方 法 重 新 使 能 窗 体 上 的 控 件 。<br />

Figure 4 中 的 CorrectMultiThreadedForm.cs 的 变 化 实 现 了 我 早 先 的 建 议 ―― 当 响 铃 操 作 在 执 行 时 你 不 想 运 行 , 就 禁 止 引 发 响 铃 操 作 的 用 户 交<br />

互 部 分 。 当 操 作 完 成 时 , 告 诉 主 线 程 重 新 使 能 被 禁 止 的 部 分 。 对 Invoke 的 调 用 是 唯 一 的 , 这 一 点 应 该 注 意 。<br />

Invoke 方 法 在 System.Windows.Forms.Controls 类 型 中 定 义 , 包 含 Form 类 型 让 类 库 中 的 所 有 派 生 控 件 都 可 使 用 该 方 法 。Invoke 方 法 的 目 的 是<br />

配 置 了 一 个 从 任 何 线 程 对 为 窗 体 或 控 件 实 现 消 息 提 取 线 程 的 调 用 。<br />

当 访 问 控 件 派 生 类 时 , 包 括 Form 类 , 从 提 取 控 件 消 息 的 线 程 来 看 你 必 须 这 样 做 。 这 在 单 线 程 的 应 用 程 序 中 是 很 自 然 的 事 情 。 但 是 当 你 从 线 程 池<br />

中 使 用 多 线 程 时 , 要 避 免 从 后 台 线 程 中 调 用 用 户 交 互 对 象 的 方 法 和 属 性 是 很 重 要 的 。 相 反 , 你 必 须 使 用 控 件 的 Invoke 方 法 间 接 的 访 问 它 们 。Invoke<br />

是 控 件 中 很 少 见 的 一 个 可 以 安 全 的 从 任 何 线 程 中 调 用 的 方 法 , 因 为 它 是 用 Win32 的 PostMessage API 实 现 的 。<br />

使 用 Control.Invoke 方 法 进 行 线 程 间 的 通 信 有 点 复 杂 。 但 是 一 旦 你 熟 悉 了 这 个 过 程 , 你 就 有 了 在 你 的 客 户 端 程 序 中 实 现 多 线 程 目 标 的 工 具 。 本<br />

栏 目 的 剩 余 部 分 将 覆 盖 其 它 一 些 细 节 , 但 是 Figure 4 中 的 CorrectMultiThreadedForm.cs 应 用 程 序 是 一 个 完 整 的 解 决 办 法 : 当 执 行 任 意 长 的 操 作<br />

时 仍 然 能 够 响 应 用 户 的 其 它 操 作 。 尽 管 大 多 数 的 用 户 交 互 被 禁 止 , 但 用 户 仍 然 可 以 重 新 配 置 和 调 整 窗 口 , 也 可 以 关 闭 程 序 。 然 而 , 用 户 不 能 任 意 使<br />

用 程 序 的 异 步 行 为 。 这 个 小 细 节 能 够 让 你 对 你 的 程 序 保 持 自 信 心 。<br />

在 我 的 第 一 个 线 程 同 步 程 序 中 , 没 有 使 用 任 何 传 统 的 线 程 结 构 , 例 如 互 斥 或 信 号 量 , 似 乎 一 钱 不 值 。 然 而 , 我 却 使 用 了 禁 止 控 件 的 最 普 通 的 方<br />

法 。<br />

细 节 - 实 现 一 个 取 消 按 钮<br />

有 时 你 想 为 你 的 用 户 提 供 一 种 取 消 长 操 作 的 方 法 。 你 所 需 要 的 就 是 你 的 主 线 程 同 后 台 线 程 之 间 的 一 些 通 信 方 法 , 通 知 后 台 线 程 操 作 不 再 被 需 要 ,<br />

可 以 停 止 。System.Threading 名 字 空 间 为 这 个 方 法 提 供 了 一 个 类 :AutoResetEvent。<br />

AutoResetEvent 是 线 程 间 通 信 的 一 种 简 单 机 制 。 一 个 AutoResetEvent 对 象 可 以 有 两 种 状 态 中 的 一 个 : 有 信 号 的 和 无 信 号 的 。 当 你 创 建 一 个<br />

AutoResetEvent 实 例 时 , 你 可 以 通 过 构 造 函 数 的 参 数 来 决 定 其 初 始 状 态 。 然 后 感 知 该 对 象 的 线 程 通 过 检 查 AutoResetEvent 对 象 的 状 态 , 或 者 用<br />

AutoResetEvent 对 象 的 Set 或 Reset 方 法 调 整 其 状 态 , 进 行 相 互 通 信 。<br />

在 某 种 程 度 上 AutoResetEvent 很 像 一 个 布 尔 类 型 , 但 是 它 提 供 的 特 征 使 其 更 适 合 于 在 线 程 间 进 行 通 信 。 这 样 的 一 个 例 子 就 是 它 有 这 种 能 力 : 一<br />

个 线 程 可 以 有 效 的 等 待 直 到 一 个 AutoResetEvent 对 象 从 一 个 无 信 号 的 状 态 变 为 有 信 号 的 状 态 。 它 是 通 过 在 该 对 象 上 调 用 WaitOne 实 现 的 。 任 何 一 个<br />

线 程 对 一 个 无 信 号 的 AutoResetEvent 对 象 调 用 了 WaitOne, 就 会 被 有 效 的 阻 塞 直 到 其 它 线 程 使 该 对 象 有 信 号 。 使 用 布 尔 变 量 线 程 必 须 在 一 个 循 环 中<br />

登 记 该 变 量 , 这 是 无 效 率 的 。 一 般 来 说 没 有 必 要 使 用 Reset 来 使 一 个 AutoResetEvent 变 为 无 信 号 , 因 为 当 其 它 线 程 感 知 到 该 对 象 为 有 信 号 时 , 它 会<br />

被 立 即 自 动 的 设 为 无 信 号 的 。<br />

现 在 你 需 要 一 种 让 你 的 后 台 线 程 无 阻 塞 的 测 试 AutoResetEvent 对 象 的 方 法 , 你 会 有 许 多 工 具 实 现 线 程 的 取 消 。 为 了 完 成 这 些 , 调 用 带 有 WaitOne


的 重 载 窗 体 并 指 出 一 个 零 毫 秒 的 超 出 时 间 , 以 零 毫 秒 为 超 出 时 间 的 WaitOne 会 立 即 返 回 , 而 不 管 AutoResetEvent 对 象 的 状 态 是 否 为 有 信 号 。 如 果 返<br />

回 值 为 true, 这 个 对 象 是 有 信 号 的 ; 否 则 由 于 时 间 超 出 而 返 回 。<br />

我 们 整 理 一 下 实 现 取 消 的 特 点 。 如 果 你 想 实 现 一 个 取 消 按 钮 , 它 能 够 取 消 后 台 线 程 中 的 一 个 长 操 作 , 按 照 以 下 步 骤 :<br />

在 你 的 窗 体 上 加 入 AutoResetEvent 域 类 型<br />

通 过 在 AutoResetEvent 的 构 造 函 数 中 传 入 false 参 数 , 设 置 该 对 象 初 始 状 态 为 无 信 号 的 。 接 着 在 你 的 窗 体 上 保 存 该 对 象 的 引 用 域 , 这 是 为 了<br />

能 够 在 窗 体 的 整 个 生 命 周 期 内 可 以 对 后 台 线 程 的 后 台 操 作 实 现 取 消 操 作 。<br />

在 你 窗 体 上 加 入 一 个 取 消 按 钮 。<br />

在 取 消 按 钮 的 Click 事 件 处 理 器 中 , 通 过 调 用 AutoResetEvent 对 象 的 Set 方 法 使 其 有 信 号 。<br />

同 时 , 在 你 的 后 台 线 程 的 逻 辑 中 周 期 性 地 在 AutoResetEvent 对 象 上 调 用 WaitOne 来 检 查 用 户 是 否 取 消 了 。<br />

if(cancelEvent.WaitOne(0, false)){<br />

// cancel operation<br />

}<br />

你 必 须 记 住 使 用 零 毫 秒 参 数 , 这 样 可 以 避 免 在 后 台 线 程 操 作 中 不 必 要 的 停 顿 。<br />

如 果 用 户 取 消 了 操 作 , 通 过 主 线 程 AutoResetEvent 会 被 设 为 有 信 号 的 。 当 WaitOne 返 回 true 时 你 的 后 台 线 程 会 得 到 警 告 , 并 停 止 操 作 。 同<br />

时 在 后 台 线 程 中 由 于 调 用 了 WaitOne 该 事 件 会 被 自 动 的 置 为 无 信 号 状 态 。<br />

为 了 能 够 看 到 取 消 长 操 作 窗 体 的 例 子 , 你 可 以 下 载 CancelableForm.cs 文 件 。 这 个 代 码 是 一 个 完 整 的 程 序 , 它 与 Figure 4 中 的<br />

CorrectMultiThreadedForm.cs 只 有 稍 微 的 不 同 。<br />

注 意 在 CancelableForm.cs 也 采 用 了 比 较 高 级 的 用 法 Control.Invoke, 在 那 里 EnableControls 方 法 被 设 计 用 来 调 用 它 自 己 如 果 当 它 被 一 个 错<br />

误 的 线 程 所 调 用 时 。 在 它 使 用 窗 体 上 的 任 何 GUI 对 象 的 方 法 或 属 性 时 要 先 做 这 个 检 查 。 这 样 能 够 使 得 EnableControls 能 够 从 任 何 线 程 中 直 接 安 全<br />

的 调 用 , 在 方 法 的 实 现 中 有 效 的 隐 藏 了 Invoke 调 用 的 复 杂 性 。 这 些 可 以 使 应 用 程 序 更 加 有 维 护 性 。 注 意 在 这 个 例 子 中 同 样 使 用 了<br />

Control.BeginInvoke, 它 是 Control.Invoke 的 异 步 版 本 。<br />

你 也 许 注 意 到 取 消 的 逻 辑 依 赖 于 后 台 线 程 通 过 WaitOne 调 用 周 期 性 的 取 消 检 查 的 能 力 。 但 是 如 果 正 在 讨 论 的 问 题 不 能 被 取 消 怎 么 办 ? 如 果 后 台<br />

操 作 是 一 个 单 个 调 用 , 像 DataAdapter.Fill, 它 会 花 很 长 时 间 ? 有 时 会 有 解 决 办 法 的 , 但 并 不 总 是 。<br />

如 果 你 的 长 操 作 根 本 不 能 取 消 , 你 可 以 使 用 一 个 伪 取 消 的 方 法 来 完 成 你 的 操 作 , 但 在 你 的 程 序 中 不 要 影 响 你 的 操 作 结 果 。 这 不 是 技 术 上 的 取 消<br />

操 作 , 它 把 一 个 可 忍 受 的 操 作 帮 定 到 一 个 线 程 池 中 , 但 这 是 在 某 种 情 况 下 的 一 种 折 中 办 法 。 如 果 你 实 现 了 类 似 的 解 决 办 法 , 你 应 该 从 你 的 取 消 按 钮<br />

事 件 处 理 器 中 直 接 使 能 你 已 禁 止 的 UI 元 素 , 而 不 要 还 依 赖 于 被 绑 定 的 后 台 线 程 通 过 Invoke 调 用 使 能 你 的 控 件 。 同 样 重 要 的 使 设 计 你 的 后 台 操 作 线<br />

程 , 当 其 返 回 时 测 试 一 下 它 是 否 被 取 消 , 以 便 它 不 影 响 现 在 被 取 消 的 操 作 的 结 果 。<br />

这 种 长 操 作 取 消 是 比 较 高 级 的 方 法 , 它 只 在 某 些 情 况 下 才 可 行 。 例 如 , 数 据 库 查 询 的 伪 取 消 就 是 这 样 , 但 是 一 个 数 据 库 的 更 新 , 删 除 , 插 入 伪<br />

取 消 是 一 个 滞 后 的 操 作 。 有 永 久 的 操 作 结 果 或 与 反 馈 有 关 的 操 作 , 像 声 音 和 图 像 , 就 不 容 易 使 用 伪 取 消 方 法 , 因 为 操 作 的 结 果 在 用 户 取 消 以 后 是 非<br />

常 明 显 的 。


更 多 细 节 - 有 关 定 时 器<br />

在 应 用 程 序 中 需 要 一 个 定 时 器 来 引 发 一 个 定 期 的 任 务 一 定 不 一 般 。 例 如 , 如 果 你 的 程 序 在 窗 体 的 状 态 条 上 显 示 当 前 时 间 , 你 可 能 每 5 秒 钟 更 新<br />

一 次 时 间 。System.Threading 名 字 空 间 包 括 了 一 个 名 为 Timer 多 线 程 定 时 器 类 。<br />

当 你 创 建 一 个 定 时 器 类 的 实 例 时 , 你 为 定 时 器 回 调 指 明 了 一 个 以 毫 秒 为 单 位 的 周 期 , 而 且 你 也 传 递 给 该 对 象 一 个 委 托 用 来 每 过 一 个 时 钟 周 期 调<br />

用 你 。 回 调 发 生 在 线 程 池 中 的 线 程 上 。 事 实 上 , 每 次 时 钟 周 期 到 来 时 真 正 发 生 的 是 一 个 工 作 条 目 在 线 程 池 中 排 队 ; 一 般 来 说 一 个 调 用 会 马 上 发 生 的 ,<br />

但 是 如 果 线 程 池 比 较 忙 , 这 个 回 调 也 许 会 在 稍 后 的 一 个 时 间 点 发 生 。<br />

如 果 你 考 虑 在 你 的 程 序 中 使 用 多 线 程 , 你 也 许 会 考 虑 使 用 定 时 器 类 。 然 而 , 如 果 你 的 程 序 使 用 了 Windows 窗 体 , 你 不 必 使 用 多 线 程 的 行 为 , 在<br />

System.Windows.Forms 名 字 空 间 中 有 另 外 一 个 也 叫 Timer 的 定 时 器 类 。<br />

System.Windows.Forms.Timer 与 其 多 线 程 的 同 伴 比 起 来 有 一 个 明 显 的 好 处 : 因 为 它 不 是 多 线 程 的 , 所 以 不 会 在 其 它 线 程 中 对 你 进 行 回 调 , 而 且<br />

更 适 合 为 应 用 程 序 提 取 窗 口 消 息 的 主 线 程 。 实 际 上 System.Windows.Forms.Timer 的 实 现 是 在 系 统 中 使 用 了 WM_TIMER 的 一 个 窗 口 消 息 。 这 种 方 法 在<br />

你 的 System.Windows.Forms.Timer 的 事 件 处 理 器 中 不 必 担 心 线 程 同 步 , 线 程 间 通 信 之 类 的 问 题 。<br />

对 于 Windows 窗 体 类 程 序 , 作 为 一 个 很 好 的 技 巧 就 是 使 用 System.Windows.Forms.Timer 类 , 除 非 你 特 别 需 要 线 程 池 中 的 线 程 对 你 进 行 回 调 。<br />

既 然 这 种 要 求 很 少 见 , 为 了 使 事 情 简 单 , 把 使 用 System.Windows.Forms.Timer 作 为 一 个 规 则 , 即 使 在 你 的 程 序 的 其 它 地 方 使 用 了 多 线 程 。<br />

展 望 将 来<br />

微 软 最 近 展 示 了 一 个 即 将 出 现 的 GUI API, 代 号 为 “Avalon”, 本 期 MSDN 杂 志 的 问 题 列 表 中 ( 见 70 页 )Charles Petzold''''s 的 文 章 描 述 了<br />

其 特 点 。 在 Avalon 框 架 中 用 户 接 口 元 素 没 有 被 系 与 一 个 特 殊 的 线 程 ; 作 为 更 换 每 个 用 户 接 口 元 素 与 一 个 单 独 的 逻 辑 线 程 上 下 文 相 关 联 , 在 UIContext<br />

类 中 实 现 。 但 是 当 你 发 现 UIContext 类 中 包 含 了 Invoke 方 法 , 及 其 姊 妹 BeginInvoke 时 , 你 就 不 会 惊 奇 了 , 在 名 字 上 与 窗 体 类 中 的 控 件 类 上 名 称 一<br />

样 的 目 的 是 说 明 他 们 在 逻 辑 作 用 上 是 一 致 的 。<br />

作 者 简 介<br />

Jason Clark 为 微 软 和 Wintellect 公 司 提 供 培 训 和 咨 询 , 他 是 Windows NT 和 Windows 2000 服 务 器 团 队 的 开 发 前 辈 。 他 是 Windows 2000 服 务<br />

器 程 序 编 程 一 书 的 合 著 者 。 与 Jason 的 联 系 方 式 :JClark@Wintellect.com


.net WinForm 控 件 的 事 件 委 托 剖 析<br />

选 择 自 120Hflashvan 的 Blog<br />

关 键 字 : Delegate, MulticastDelegate, EventHandler, EventHandlerList, EventHandlerList.ListEntry, Control,<br />

Component<br />

首 先 从 controlInstance.Click 事 件 开 始 . 用 Reflector 反 编 译 System.Windows.Forms.Control 类 可 以 看 到 对<br />

Click 事 件 的 定 义 :<br />

[System.Windows.Forms.SRCategory("CatAction"), System.Windows.Forms.SRDescription("Contr<br />

olOnClickDescr")]<br />

public event EventHandler Click<br />

{<br />

add<br />

{<br />

base.Events.AddHandler(Control.EventClick, value);<br />

}<br />

remove<br />

{<br />

base.Events.RemoveHandler(Control.EventClick, value);<br />

}<br />

}<br />

这 里 的 Control.EventClick 是 一 个 只 读 的 静 态 私 有 属 性 , 它 是 以 后 事 件 查 找 委 托 的 键 (key), 请 记 住 这 个 .<br />

private static readonly object EventClick = new object();<br />

Control 的 Events 属 性 是 由 System.ComponentModel.Component 继 承 而 来 , 它 是 EventHandlerList 的 实 例 .<br />

private EventHandlerList events;


protected EventHandlerList Events<br />

{<br />

get<br />

{<br />

if (this.events == null)<br />

{<br />

this.events = new EventHandlerList();<br />

}<br />

return this.events;<br />

}<br />

}<br />

EventHandlerList 类 有 三 个 重 要 的 方 法 :<br />

public void AddHandler(object key, Delegate value);<br />

public void RemoveHandler(object key, Delegate value);<br />

private ListEntry Find(object key);<br />

AddHandler 的 作 用 是 插 入 一 个 键 和 一 个 委 托 类 型 的 值 , 插 入 之 前 通 过 Find 方 法 检 查 一 下 同 样 的 委 托 对 象<br />

是 否 存 在 , 如 果 存 在 , 则 合 并 ; 如 果 不 存 在 , 以 要 插 入 的 委 托 对 象 (value) 为 头 .<br />

public void AddHandler(object key, Delegate value)<br />

{<br />

EventHandlerList.ListEntry entry1 = this.Find(key);<br />

if (entry1 != null)<br />

{<br />

entry1.handler = Delegate.Combine(entry1.handler, value);<br />

}<br />

else


{<br />

this.head = new EventHandlerList.ListEntry(key, value, this.head);<br />

}<br />

}<br />

如 果 是 一 个 按 钮 的 Click 事 件 , 我 们 一 般 定 义 为 :<br />

button1.Click += new EventHandler(OnButtonClick);<br />

protected void OnButtonClick(object sender, System.EventArgs e)<br />

{<br />

// 你 的 处 理 函 数<br />

}<br />

则 通 过 了 button1.Events.AddHandler(Control.EventClick, EventHandler handler), 而 这 个 handler 却 是 一<br />

个 MulticastDelegate 的 实 例 。 看 MS 公 布 的 .net framework 部 分 源 码 就 知 道 了 :<br />

// ==++==<br />

//<br />

//<br />

// Copyright (c) 2002 Microsoft Corporation. All rights reserved.<br />

//<br />

// The use and distribution terms for this software are contained in the file<br />

// named license.txt, which can be found in the root of this distribution.<br />

// By using this software in any fashion, you are agreeing to be bound by the<br />

// terms of this license.<br />

//<br />

// You must not remove this notice, or any other, from this software.


//<br />

// ==--==<br />

namespace System {<br />

using System;<br />

/// <br />

[Serializable()]<br />

public delegate void EventHandler(Object sender, EventArgs e);}<br />

现 在 我 们 转 到 对 委 托 (Delegate) 和 多 播 委 托 (MulticastDelegate) 的 研 究 了 。<br />

Delegate 类 已 经 封 装 好 产 生 委 托 , 消 除 委 托 和 执 行 委 法 的 方 法 , 它 是 一 个 不 能 实 例 化 的 抽 象 类 。 但 .net 的<br />

编 译 器 支 持 由 delegate 定 义 的 类 型 来 实 例 化 一 个 Delegate 对 象 , 它 能 让 这 个 对 象 的 执 行 委 托 方 法 像 普 通<br />

函 数 一 样 调 用 ( 具 体 的 可 以 看 C# 高 级 编 程 里 面 的 实 例 ), 所 以 很 多 时 候 ,delegate 类 型 会 被 认 为 是 函 数 指 针 。<br />

Delegate 还 有 两 个 很 重 要 的 方 法 , 组 合 委 托 Combine 和 删 除 委 托 Remove。 在 单 播 委 托 Delegate 中 使 用<br />

这 组 合 委 托 方 法 会 抛 出 多 播 不 支 持 异 常 (MulticastNotSupportedException)。 而 使 用 删 除 委 托 方 法 时 , 如 果<br />

这 个 单 播 委 托 和 要 删 除 的 委 托 是 同 一 个 值 时 , 则 返 回 null, 证 明 已 经 删 除 ; 如 果 不 是 , 则 原 封 不 动 返 回 原<br />

来 的 单 播 委 托 。<br />

EventHandler 实 际 上 是 一 个 多 播 委 托 实 例 , 所 以 它 支 持 组 合 委 托 和 删 除 委 托 的 方 法 。 这 个 实 例 , 实 际 上 是<br />

一 个 委 托 实 例 链 , 它 是 这 个 链 的 链 尾 。 每 次 像 调 用 普 通 函 数 调 用 这 个 委 托 的 时 候 , 这 个 委 托 会 执 行 完 委 托<br />

的 代 理 函 数 , 并 查 找 链 表 中 上 一 个 委 托 实 例 , 执 行 这 个 委 托 的 代 理 函 数 , 再 查 找 链 表 中 上 上 个 委 托 实 例 ,<br />

执 行 当 前 委 托 的 代 理 函 数 。。。 一 直 到 链 表 被 遍 历 完 。<br />

protected override sealed Object DynamicInvokeImpl(Object[] args)<br />

{<br />

if (_prev != null)<br />

_prev.DynamicInvokeImpl(args);<br />

return base.DynamicInvokeImpl(args);<br />

}


好 了 。 那 可 以 想 象 , 一 个 用 户 点 击 按 钮 button1, 首 先 执 行 的 函 数 是 OnClick 函 数<br />

[EditorBrowsable(EditorBrowsableState.Advanced)]<br />

protected virtual void OnClick(EventArgs e)<br />

{<br />

if (this.CanRaiseEvents)<br />

{<br />

EventHandler handler1 = (EventHandler) base.Events[Control.EventClick];<br />

if (handler1 != null)<br />

{<br />

handler1(this, e);<br />

}<br />

}<br />

}<br />

handler1 就 是 一 个 多 播 委 托 , 如 果 不 为 空 , 则 执 行 它 , 而 且 这 个 执 行 就 是 执 行 所 有 的 代 理 函 数 。 这 样 就<br />

明 白 了 WinForm 控 件 Click 事 件 所 有 的 始 终 了 !<br />

参 考 文 章 : 121Hhttp://blog.sunmast.com/Sunmast/archive/2005/04/21/1769.aspx<br />

以 上 是 个 人 观 点 , 由 于 时 间 仓 促 及 个 人 水 平 有 限 , 难 免 错 误 , 敬 请 斧 正 !


c# 中 使 用 多 线 程 ( 图 )<br />

选 择 自 122Hiuhxq 的 Blog<br />

using System;<br />

using System.Drawing;<br />

using System.Collections;<br />

using System.ComponentModel;<br />

using System.Windows.Forms;<br />

using System.Data;<br />

using System.Threading;<br />

namespace student<br />

{<br />

/// <br />

/// Form1 的 摘 要 说 明 。<br />

/// <br />

public class Form1 : System.Windows.Forms.Form<br />

{<br />

private System.Windows.Forms.Button button1;<br />

private System.Windows.Forms.Button button2;<br />

private System.Windows.Forms.Button button3;<br />

ArrayList threads = new ArrayList();<br />

private System.Windows.Forms.ListView listView1;<br />

private System.Windows.Forms.ColumnHeader columnHeader1;<br />

private System.Windows.Forms.ColumnHeader columnHeader2;<br />

/// <br />

/// 必 需 的 设 计 器 变 量 。<br />

/// <br />

private System.ComponentModel.Container components = null;<br />

public Form1()<br />

{<br />

//<br />

// Windows 窗 体 设 计 器 支 持 所 必 需 的<br />

//<br />

InitializeComponent();<br />

//<br />

// TODO: 在 InitializeComponent 调 用 后 添 加 任 何 构 造 函 数 代 码<br />

//<br />

}<br />

/// <br />

/// 清 理 所 有 正 在 使 用 的 资 源 。<br />

/// <br />

protected override void Dispose( bool disposing )<br />

{<br />

if( disposing )<br />

{


if (components != null)<br />

{<br />

components.Dispose();<br />

}<br />

}<br />

base.Dispose( disposing );<br />

}<br />

#region Windows 窗 体 设 计 器 生 成 的 代 码<br />

/// <br />

/// 设 计 器 支 持 所 需 的 方 法 - 不 要 使 用 代 码 编 辑 器 修 改<br />

/// 此 方 法 的 内 容 。<br />

/// <br />

private void InitializeComponent()<br />

{<br />

this.button1 = new System.Windows.Forms.Button();<br />

this.button2 = new System.Windows.Forms.Button();<br />

this.button3 = new System.Windows.Forms.Button();<br />

this.listView1 = new System.Windows.Forms.ListView();<br />

this.columnHeader1 = new System.Windows.Forms.ColumnHeader();<br />

this.columnHeader2 = new System.Windows.Forms.ColumnHeader();<br />

this.SuspendLayout();<br />

//<br />

// button1<br />

//<br />

this.button1.Location = new System.Drawing.Point(24, 216);<br />

this.button1.Name = "button1";<br />

this.button1.TabIndex = 1;<br />

this.button1.Text = "Add";<br />

this.button1.Click += new System.EventHandler(this.button1_Click);<br />

//<br />

// button2<br />

//<br />

this.button2.Location = new System.Drawing.Point(104, 216);<br />

this.button2.Name = "button2";<br />

this.button2.TabIndex = 2;<br />

this.button2.Text = "Del";<br />

this.button2.Click += new System.EventHandler(this.button2_Click);<br />

//<br />

// button3<br />

//<br />

this.button3.Location = new System.Drawing.Point(184, 216);<br />

this.button3.Name = "button3";<br />

this.button3.TabIndex = 3;<br />

this.button3.Text = "DelAll";


this.button3.Click += new System.EventHandler(this.button3_Click);<br />

//<br />

// listView1<br />

//<br />

this.listView1.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {<br />

this.columnHeader1,<br />

this.columnHeader2});<br />

this.listView1.FullRowSelect = true;<br />

this.listView1.GridLines = true;<br />

this.listView1.Location = new System.Drawing.Point(0, 0);<br />

this.listView1.Name = "listView1";<br />

this.listView1.Size = new System.Drawing.Size(288, 208);<br />

this.listView1.TabIndex = 5;<br />

this.listView1.View = System.Windows.Forms.View.Details;<br />

//<br />

// columnHeader1<br />

//<br />

this.columnHeader1.Text = " 线 程 编 号 ";<br />

this.columnHeader1.Width = 81;<br />

//<br />

// columnHeader2<br />

//<br />

this.columnHeader2.Text = "value";<br />

this.columnHeader2.Width = 180;<br />

//<br />

// Form1<br />

//<br />

this.AutoScaleBaseSize = new System.Drawing.Size(6, 14);<br />

this.ClientSize = new System.Drawing.Size(292, 266);<br />

this.Controls.Add(this.listView1);<br />

this.Controls.Add(this.button3);<br />

this.Controls.Add(this.button2);<br />

this.Controls.Add(this.button1);<br />

this.Name = "Form1";<br />

this.Text = "Form1";<br />

this.Load += new System.EventHandler(this.Form1_Load);<br />

this.ResumeLayout(false);<br />

}<br />

#endregion<br />

/// <br />

/// 应 用 程 序 的 主 入 口 点 。<br />

/// <br />

[STAThread]<br />

static void Main()


{<br />

Application.Run(new Form1());<br />

}<br />

private void Form1_Load(object sender, System.EventArgs e)<br />

{<br />

}<br />

private void button1_Click(object sender, System.EventArgs e)<br />

{<br />

Add();<br />

}<br />

private void button2_Click(object sender, System.EventArgs e)<br />

{<br />

Del();<br />

}<br />

private void button3_Click(object sender, System.EventArgs e)<br />

{<br />

while(threads.Count>0)<br />

{<br />

Thread t1 = (Thread)threads[0];<br />

if(t1.IsAlive)<br />

{<br />

t1.Abort();<br />

}<br />

threads.RemoveAt(0);<br />

lock(listView1)<br />

{<br />

listView1.Items.RemoveAt(0);<br />

}<br />

}<br />

}<br />

private void Add()<br />

{<br />

int count = threads.Count;<br />

if(count


}<br />

}<br />

private void Del()<br />

{<br />

int count = threads.Count;<br />

if(count>0)<br />

{<br />

Thread t1 = (Thread)threads[count-1];<br />

if(t1.IsAlive)<br />

{<br />

t1.Abort();<br />

}<br />

threads.RemoveAt(count-1);<br />

lock(listView1)<br />

{<br />

listView1.Items.RemoveAt(count-1);<br />

}<br />

}<br />

}<br />

private void Process()<br />

{<br />

int i = 1;<br />

while(true)<br />

{<br />

i++;<br />

int j = threads.IndexOf(Thread.CurrentThread);<br />

lock(listView1)<br />

{<br />

listView1.Items[j].SubItems[1].Text = i.ToString();<br />

}<br />

Thread.Sleep(0);<br />

}<br />

}<br />

}<br />

}<br />

作 者 Blog:123Hhttp://blog.csdn.net/iuhxq/<br />

相 关 文 章<br />

124H[ 原 创 ]c# 中 使 用 多 线 程 ( 图 ) 二<br />

125HSQL 存 储 过 程 & 算 法<br />

126H 取 汉 字 拼 音 首 字 母 的 存 储 过 程<br />

127HDataGrid 自 定 义 分 页 存 储 过 程


利 用 Visual C# 打 造 一 个 平 滑 的 进 度 条<br />

选 择 自 128Hswazn_yj 的 Blog<br />

利 用 Visual C# 打 造 一 个 平 滑 的 进 度 条<br />

概 述<br />

本 文 描 述 了 如 何 建 立 一 个 简 单 的 、 自 定 义 的 用 户 控 件 —— 一 个 平 滑 的 进 度<br />

条 。<br />

在 早 先 的 进 度 条 控 件 版 本 中 , 例 如 在 Microsoft Windows Common Controls<br />

ActiveX 控 件 中 提 供 的 版 本 , 您 可 以 看 到 进 度 条 有 两 种 不 同 的 视 图 。 您 可 以 通 过<br />

设 定 Scrolling 属 性 来 设 定 Standard 视 图 或 是 Smooth 视 图 。 Smooth 视 图<br />

提 供 了 一 个 区 域 来 平 滑 的 显 示 进 度 ,Standard 试 图 则 看 上 去 是 由 一 个 一 个 方 块<br />

来 表 示 进 度 的 。<br />

在 Visual C# .NET 中 提 供 的 进 度 条 控 件 只 支 持 Standard 视 图 。<br />

本 文 的 代 码 样 例 揭 示 了 如 何 建 立 一 个 有 如 下 属 性 的 控 件 :<br />

Minimum。 该 属 性 表 示 了 进 度 条 的 最 小 值 。 默 认 情 况 下 是 0 ; 您 不 能 将 该<br />

属 性 设 为 负 值 。<br />

Maximum。 该 属 性 表 示 了 进 度 条 的 最 大 值 。 默 认 情 况 下 是 100 。<br />

Value。 该 属 性 表 示 了 进 度 条 的 当 前 值 。 该 值 必 须 介 于 Minimum 和<br />

Maximum 之 间 。<br />

ProgressBarColor。 该 属 性 表 示 了 进 度 条 的 颜 色 。<br />

建 立 一 个 自 定 义 的 进 度 条 控 件<br />

1、 按 着 下 面 的 步 骤 , 在 Visual C# .NET 中 建 立 一 个 Windows Control<br />

Library 项 目 :<br />

a、 打 开 Microsoft Visual Studio .NET。<br />

b、 点 击 File 菜 单 , 点 击 New , 再 点 击 Project 。<br />

c、 在 New Project 对 话 框 中 , 在 Project Types 中 选 择 Visual C#<br />

Projects, 然 后 在 Templates 中 选 择 Windows Control Library 。<br />

d、 在 Name 框 中 , 填 上 SmoothProgressBar , 并 点 击 OK 。<br />

e、 在 Project Explorer 中 , 重 命 名 缺 省 的 class module , 将<br />

UserControl1.cs 改 为 SmoothProgressBar.cs 。


f、 在 该 UserControl 对 象 的 Property 窗 口 中 , 将 其 Name 属 性 从<br />

UserControl1 改 为 SmoothProgressBar 。<br />

2、 此 时 , 您 已 经 从 control 类 继 承 了 一 个 新 类 , 并 可 以 添 加 新 的 功 能 。 但<br />

是 ,ProgressBar 累 是 密 封 (sealed) 的 , 不 能 再 被 继 承 。 因 此 , 您 必 须 从 头 开 始<br />

建 立 这 个 控 件 。<br />

将 下 面 的 代 码 添 加 到 UserControl 模 块 中 , 就 在 “Windows Form Designer<br />

generated code” 之 后 :<br />

int min = 0; // Minimum value for progress range<br />

int max = 100; // Maximum value for progress range<br />

int val = 0; // Current progress<br />

Color BarColor = Color.Blue; // Color of progress meter<br />

protected override void OnResize(EventArgs e)<br />

{<br />

// Invalidate the control to get a repaint.<br />

this.Invalidate();<br />

}<br />

protected override void OnPaint(PaintEventArgs e)<br />

{<br />

Graphics g = e.Graphics;<br />

SolidBrush brush = new SolidBrush(BarColor);<br />

float percent = (float)(val - min) / (float)(max - min);<br />

Rectangle rect = this.ClientRectangle;<br />

// Calculate area for drawing the progress.<br />

rect.Width = (int)((float)rect.Width * percent);<br />

// Draw the progress meter.<br />

g.FillRectangle(brush, rect);<br />

// Draw a three-dimensional border around the control.<br />

Draw3DBorder(g);<br />

}<br />

// Clean up.<br />

brush.Dispose();<br />

g.Dispose();<br />

public int Minimum


{<br />

get<br />

{<br />

return min;<br />

}<br />

set<br />

{<br />

// Prevent a negative value.<br />

if (value < 0)<br />

{<br />

min = 0;<br />

}<br />

// Make sure that the minimum value is never set higher than<br />

the maximum value.<br />

if (value > max)<br />

{<br />

min = value;<br />

min = value;<br />

}<br />

// Ensure value is still in range<br />

if (val < min)<br />

{<br />

val = min;<br />

}<br />

}<br />

}<br />

// Invalidate the control to get a repaint.<br />

this.Invalidate();<br />

public int Maximum<br />

{<br />

get<br />

{<br />

return max;<br />

}<br />

set<br />

{<br />

// Make sure that the maximum value is never set lower than<br />

the minimum value.


if (value < min)<br />

{<br />

min = value;<br />

}<br />

max = value;<br />

// Make sure that value is still in range.<br />

if (val > max)<br />

{<br />

val = max;<br />

}<br />

}<br />

}<br />

// Invalidate the control to get a repaint.<br />

this.Invalidate();<br />

public int Value<br />

{<br />

get<br />

{<br />

return val;<br />

}<br />

set<br />

{<br />

int oldValue = val;<br />

// Make sure that the value does not stray outside the valid<br />

range.<br />

if (value < min)<br />

{<br />

val = min;<br />

}<br />

else if (value > max)<br />

{<br />

val = max;<br />

}<br />

else<br />

{<br />

val = value;<br />

}


Invalidate only the changed area.<br />

float percent;<br />

Rectangle newValueRect = this.ClientRectangle;<br />

Rectangle oldValueRect = this.ClientRectangle;<br />

// Use a new value to calculate the rectangle for progress.<br />

percent = (float)(val - min) / (float)(max - min);<br />

newValueRect.Width = (int)((float)newValueRect.Width *<br />

percent);<br />

// Use an old value to calculate the rectangle for progress.<br />

percent = (float)(oldValue - min) / (float)(max - min);<br />

oldValueRect.Width = (int)((float)oldValueRect.Width *<br />

percent);<br />

Rectangle updateRect = new Rectangle();<br />

// Find only the part of the screen that must be updated.<br />

if (newValueRect.Width > oldValueRect.Width)<br />

{<br />

updateRect.X = oldValueRect.Size.Width;<br />

updateRect.Width = newValueRect.Width -<br />

oldValueRect.Width;<br />

}<br />

else<br />

{<br />

updateRect.X = newValueRect.Size.Width;<br />

updateRect.Width = oldValueRect.Width -<br />

newValueRect.Width;<br />

}<br />

updateRect.Height = this.Height;<br />

}<br />

}<br />

// Invalidate the intersection region only.<br />

this.Invalidate(updateRect);<br />

public Color ProgressBarColor<br />

{<br />

get<br />

{<br />

return BarColor;


}<br />

set<br />

{<br />

BarColor = value;<br />

}<br />

}<br />

// Invalidate the control to get a repaint.<br />

this.Invalidate();<br />

private void Draw3DBorder(Graphics g)<br />

{<br />

int PenWidth = (int)Pens.White.Width;<br />

g.DrawLine(Pens.DarkGray, new<br />

Point(this.ClientRectangle.Left, this.ClientRectangle.Top),<br />

new Point(this.ClientRectangle.Width - PenWidth,<br />

this.ClientRectangle.Top));<br />

g.DrawLine(Pens.DarkGray, new<br />

Point(this.ClientRectangle.Left, this.ClientRectangle.Top), new<br />

Point(this.ClientRectangle.Left, this.ClientRectangle.Height -<br />

PenWidth));<br />

g.DrawLine(Pens.White, new Point(this.ClientRectangle.Left,<br />

this.ClientRectangle.Height - PenWidth),<br />

new Point(this.ClientRectangle.Width - PenWidth,<br />

this.ClientRectangle.Height - PenWidth));<br />

g.DrawLine(Pens.White, new Point(this.ClientRectangle.Width -<br />

PenWidth, this.ClientRectangle.Top),<br />

new Point(this.ClientRectangle.Width - PenWidth,<br />

this.ClientRectangle.Height - PenWidth));<br />

}<br />

3、 在 Build 菜 单 中 , 点 击 Build Solution 来 编 译 整 个 项 目 。<br />

建 立 一 个 简 单 的 客 户 端 应 用<br />

1、 在 File 菜 单 中 , 点 击 New , 再 点 击 Project。<br />

2、 在 Add New Project 对 话 框 中 , 在 Project Types 中 点 击 Visual C#<br />

Projects, 在 Templates 中 点 击 Windows Application, 并 点 击 OK。<br />

3、 按 照 下 面 的 步 骤 , 在 Form 上 添 加 两 个 SmoothProgressBar 实 例 :


a、 在 Tools 菜 单 上 , 点 击 Customize Toolbox。<br />

b、 点 击 .NET Framework Components 页 。<br />

c、 点 击 Browse, 然 后 选 中 你 在 Create a Custom ProgressBar Control 段<br />

中 建 立 的 SmoothProgressBar.dll 文 件 。<br />

d、 点 击 OK。 您 可 以 看 到 在 toolbox 中 已 经 有 SmoothProgressBar 控 件 了 。<br />

e、 从 toolbox 中 拖 两 个 SmoothProgressBar 控 件 的 实 例 到 该 Windows<br />

Application 项 目 中 的 默 认 form 上 。<br />

4、 从 toolbox 页 中 拖 一 个 Timer 控 件 到 form 上 。<br />

5、 将 下 面 的 代 码 添 加 到 Timer 控 件 的 Tick 事 件 中 :<br />

if (this.smoothProgressBar1.Value > 0)<br />

{<br />

this.smoothProgressBar1.Value--;<br />

this.smoothProgressBar2.Value++;<br />

}<br />

else<br />

{<br />

this.timer1.Enabled = false;<br />

}<br />

6、 从 toolbox 页 中 拖 一 个 Button 控 件 到 form 上 。<br />

7、 将 下 面 的 代 码 添 加 到 Button 控 件 的 Click 事 件 中 :<br />

this.smoothProgressBar1.Value = 100;<br />

this.smoothProgressBar2.Value = 0;<br />

this.timer1.Interval = 1;<br />

this.timer1.Enabled = true;<br />

8、 在 Debug 菜 单 中 , 点 击 Start 来 运 行 样 例 项 目 。<br />

9、 点 击 Button。 注 意 观 察 那 两 个 进 度 指 示 器 。 一 个 逐 渐 减 小 , 另 一 个 逐 渐<br />

增 加 。<br />

作 者 Blog:129Hhttp://blog.csdn.net/swazn_yj/


130H Windows<br />

窗 体 多 线 程<br />

Windows 窗 体 多 线 程<br />

当 我 们 在 编 写 一 个 需 要 长 时 间 运 行 的 程 序 时 ( 如 数 学 计 算 , 执 行 数 据 库 命 令 , 访 问<br />

WebService)<br />

常 常 将 它 们 写 在 一 个 组 件 中 , 让 他 们 在 后 台 运 行 . 从 而 不 影 响 Windows 界 面 的 显 示 和 界 面 上 的<br />

交<br />

互 操 作 . 但 我 们 有 时 还 是 感 到 不 怎 方 便 , 如 我 们 不 能 直 接 应 用 winForm 里 定 义 的 变 量 等 . 那 么 在<br />

UI 进 程 中 能 否 直 接 执 行 长 时 间 运 行 的 程 序 , 而 不 影 响 UI 进 程 呢 ?<br />

下 面 的 示 例 将 解 决 这 个 问 题 .<br />

本 例 利 用 多 线 程 从 长 时 间 运 行 的 操 作 ( 计 算 fbnc 数 列 (n>36)) 中 分 离 出 用 户 界 面 (UI),<br />

以 将 用 户 的 后 续 输 入 传 递 给 辅 助 线 程 (CalHandler,showDel) 以 调 节 其 行 为 与 用 户 界 面 元 素<br />

进 行 交 互 , 从 而 实 现 稳 定 而 正 确 的 多 线 程 处 理 的 消 息 传 递 方<br />

案 。<br />

using System;<br />

using System.Collections.Generic;<br />

using System.ComponentModel;<br />

using System.Data;<br />

using System.Drawing;<br />

using System.Text;<br />

using System.Windows.Forms;<br />

using System.Threading;<br />

using System.Runtime.Remoting;


using System.Runtime.Remoting.Messaging;<br />

namespace AsynchCalcPi<br />

{<br />

public partial class Form2 : Form<br />

{<br />

public Form2()<br />

{<br />

InitializeComponent();<br />

Form2.calComplete += new CalHandler(Form2_calComplete);<br />

}<br />

void Form2_calComplete(string strTemp)<br />

{<br />

// 为 了 对 调 用 者 屏 蔽 与 此 UI 线 程 有 关 的 线 程 安 全 通 信 信 息 ,<br />

//ShowCalcResult 方 法 在 此 UI 线 程 上 通 过 Control.BeginInvoke 方 法 使<br />

用 showDel 给 自 己 发 送 消 息 。<br />

//Control.BeginInvoke 异 步 队 列 为 UI 线 程 提 供 服 务 , 并 且 不 等 待 结 果 就 继 续 运<br />

行 。<br />

if(!bClose )<br />

this.BeginInvoke(new showDel(showRes ),strTemp );<br />

}<br />

int times = 1;<br />

private void showRes(string strTemp)<br />

{<br />

times += 1;<br />

this.richTextBox1.AppendText("," + strTemp);<br />

this.progressBar1.Value = iStep * times%100;<br />

if (FinishFlag)<br />

{<br />

//timer1.Enabled = false;<br />

MessageBox.Show(strTemp);<br />

}<br />

}<br />

private delegate void showDel(string stemp);


private void button1_Click(object sender, EventArgs e)<br />

{<br />

try<br />

{<br />

",");<br />

j = Int32.Parse(this.textBox_Num.Text.Trim());<br />

iStep = 100 / j;<br />

if (j < 1)<br />

return;<br />

}<br />

catch<br />

{<br />

MessageBox.Show(" 请 在 文 本 框 内 输 入 数 字 字 符 ");<br />

return;<br />

}<br />

for (int i = 0; i < j; i++)<br />

this.richTextBox1.AppendText(this.ComputeFibonacci(i).ToString() +<br />

}<br />

private long ComputeFibonacci(int n)<br />

{<br />

//' The parameter n must be >= 0 and


{<br />

if (i 0 && i


teFibonacci);<br />

calcFbnc.BeginInvoke(j, callBack, null);<br />

}<br />

// 实 时 显 示 通 知 服 务<br />

private long ShowCalcResult(int n)<br />

{<br />

long result1 = 0;<br />

for (int i = 0; i < n; i++)<br />

{<br />

result1 = this.ComputeFibonacci(i);<br />

// 委 托 calComplete 由 辅 助 线 程 用 于 向 UI 线 程 回 传 消 息 , 通 常 是 有 关 长 时 间 运<br />

行 的 操 作 的 最 新 进 度 。<br />

calComplete(result1.ToString() );<br />

}<br />

return result1;<br />

}<br />

// 定 义 计 算 过 程 中 用 于 传 递 消 息 的 委 托<br />

public delegate void CalHandler(string strTemp);<br />

// 定 义 事 件<br />

public static event CalHandler calComplete;<br />

// 定 义 委 托 进 行 异 步 计 算 Fibonacci 数 列<br />

private delegate long ComputeFibonacciDel(int n);<br />

态 .<br />

gate;<br />

// 定 义 引 用 在 异 步 操 作 完 成 时 调 用 的 回 调 方 法 . 用 以 在 计 算 完 成 后 取 得 返 回 值 和 当 前 状<br />

AsyncCallback callBack = new AsyncCallback(ShowResult);<br />

private static bool FinishFlag = false;<br />

static void ShowResult(IAsyncResult ar)<br />

{<br />

// Asynchronous Callback method.<br />

// Obtains the last parameter of the delegate call.<br />

int value = Convert.ToInt32(ar.AsyncState);<br />

// Obtains return value from the delegate call using EndInvoke.<br />

AsyncResult aResult = (AsyncResult)ar;<br />

ComputeFibonacciDel temp = (ComputeFibonacciDel)aResult.AsyncDele<br />

long result = temp.EndInvoke(ar);<br />

FinishFlag = true;<br />

calComplete(" 当 前 状 态 代 号 :" + value.ToString() + " " + " 计 算 后 的 返 回 结


果 :" + result.ToString());<br />

}<br />

int i = 0;<br />

private void timer1_Tick(object sender, EventArgs e)<br />

{<br />

i += 1;<br />

i = i % 100;<br />

this.progressBar1.Value = i;<br />

}<br />

int j = 0;<br />

int iStep = 1;<br />

ComputeFibonacciDel calcFbnc;<br />

private void button4_Click(object sender, EventArgs e)<br />

{<br />

FinishFlag = false;<br />

// 停 止 进 度 条 的 自 动 滚 动 . 让 进 度 条 根 据 当 前 进 度 显 示<br />

this.timer1.Enabled = false;<br />

this.progressBar1.Value = 0;<br />

try<br />

{<br />

j= Int32.Parse(this.textBox_Num.Text.Trim());<br />

iStep = 100 / j ;<br />

if (j < 1)<br />

return;<br />

}<br />

catch<br />

{<br />

MessageBox.Show(" 请 在 文 本 框 内 输 入 数 字 字 符 ");<br />

return;<br />

}<br />

//ComputeFibonacciDel, 用 于 捆 绑 要 传 递 给 ( 从 线 程 池 中 分 配 的 ) 辅 助 线 程 上 的<br />

ShowCalcResult 的 参 数 。<br />

// 当 用 户 决 定 要 计 算 fbnc 数 列 时 , 事 件 处 理 程 序 将 创 建 此 委 托 的 一 个 实 例 。<br />

// 此 工 作 通 过 调 用 BeginInvoke 在 线 程 池 中 进 行 排 队 。 该 委 托 实 际 上 是 由 UI 线 程<br />

用 于 向 辅 助 线 程 传 递 消 息 。<br />

calcFbnc = new ComputeFibonacciDel(this.ShowCalcResult );<br />

IAsyncResult aResult= calcFbnc.BeginInvoke(j,callBack , null);


131H .NET<br />

异<br />

// 已 在 callBack 方 法 中 写 出 , 此 处 不 再 写 此 方 法 .<br />

////Wait for the call to complete<br />

//aResult.AsyncWaitHandle.WaitOne();<br />

//long callResult = calcFbnc.EndInvoke(aResult);<br />

}<br />

bool bClose = false;<br />

private void Form2_FormClosing(object sender, FormClosingEventArgs e)<br />

{<br />

bClose = true;<br />

}<br />

Framework 异 步 调 用<br />

.NET Framework 异 步 调 用<br />

.NET Framework 允 许 您 异 步 调 用 任 何 方 法 。 定 义 与 您 需 要 调 用 的 方 法 具 有 相 同 签 名 的 委 托 ; 公 共 语 言 运 行<br />

库 将 自 动 为 该 委 托 定 义 具 有 适 当 签 名 的 BeginInvoke 和 EndInvoke 方 法 。<br />

BeginInvoke 方 法 用 于 启 动 异 步 调 用 。 它 与 您 需 要 异 步 执 行 的 方 法 具 有 相 同 的 参 数 , 只 不 过 还 有 两 个 额 外<br />

的 参 数 ( 将 在 稍 后 描 述 )。BeginInvoke 立 即 返 回 , 不 等 待 异 步 调 用 完 成 。BeginInvoke 返 回 IasyncResult,<br />

可 用 于 监 视 调 用 进 度 。<br />

EndInvoke 方 法 用 于 检 索 异 步 调 用 结 果 。 调 用 BeginInvoke 后 可 随 时 调 用 EndInvoke 方 法 ; 如 果 异 步 调<br />

用 未 完 成 ,EndInvoke 将 一 直 阻 塞 到 异 步 调 用 完 成 。EndInvoke 的 参 数 包 括 您 需 要 异 步 执 行 的 方 法 的 out<br />

和 ref 参 数 ( 在 Visual Basic 中 为 ByRef 和 ByRef) 以 及 由 BeginInvoke 返 回 的 IAsyncResult。<br />

注 意 Visual Studio .NET 中 的 智 能 感 知 功 能 会 显 示 BeginInvoke 和 EndInvoke 的 参 数 。 如 果 您 没 有 使 用<br />

Visual Studio 或 类 似 的 工 具 , 或 者 您 使 用 的 是 C# 和 Visual Studio .NET, 请 参 见 132H 步 方 法 签 名 获 取 有 关<br />

运 行 库 为 这 些 方 法 定 义 的 参 数 的 描 述 。<br />

本 主 题 中 的 代 码 演 示 了 四 种 使 用 BeginInvoke 和 EndInvoke 进 行 异 步 调 用 的 常 用 方 法 。 调 用 了<br />

BeginInvoke 后 , 可 以 :<br />

• 进 行 某 些 操 作 , 然 后 调 用 EndInvoke 一 直 阻 塞 到 调 用 完 成 。<br />

• 使 用 IAsyncResult.AsyncWaitHandle 获 取 WaitHandle, 使 用 它 的 WaitOne 方 法 将 执 行 一 直 阻<br />

塞 到 发 出 WaitHandle 信 号 , 然 后 调 用 EndInvoke。<br />

• 轮 询 由 BeginInvoke 返 回 的 IAsyncResult, 确 定 异 步 调 用 何 时 完 成 , 然 后 调 用 EndInvoke。<br />

• 将 用 于 回 调 方 法 的 委 托 传 递 给 BeginInvoke。 该 方 法 在 异 步 调 用 完 成 后 在 ThreadPool 线 程 上 执<br />

行 , 它 可 以 调 用 EndInvoke。<br />

警 告 始 终 在 异 步 调 用 完 成 后 调 用 EndInvoke。<br />

测 试 方 法 和 异 步 委 托


四 个 示 例 全 部 使 用 同 一 个 长 期 运 行 的 测 试 方 法 TestMethod。 该 方 法 显 示 一 个 表 明 它 已 开 始 处 理 的 控 制 台 信<br />

息 , 休 眠 几 秒 钟 , 然 后 结 束 。TestMethod 有 一 个 out 参 数 ( 在 Visual Basic 中 为 ByRef), 它 演 示<br />

了 如 何 将 这 些 参 数 添 加 到 BeginInvoke 和 EndInvoke 的 签 名 中 。 您 可 以 用 类 似 的 方 式 处 理 ref 参 数 ( 在<br />

Visual Basic 中 为 ByRef)。<br />

下 面 的 代 码 示 例 显 示 TestMethod 以 及 代 表 TestMethod 的 委 托 ; 若 要 使 用 任 一 示 例 , 请 将 示 例 代 码 追 加<br />

到 这 段 代 码 中 。<br />

注 意 为 了 简 化 这 些 示 例 ,TestMethod 在 独 立 于 Main() 的 类 中 声 明 。 或 者 ,TestMethod 可 以 是 包 含 Main()<br />

的 同 一 类 中 的 static 方 法 ( 在 Visual Basic 中 为 Shared)。<br />

using System;<br />

using System.Threading;<br />

public class AsyncDemo {<br />

// The method to be executed asynchronously.<br />

//<br />

public string TestMethod(int callDuration, out int threadId) {<br />

Console.WriteLine("Test method begins.");<br />

Thread.Sleep(callDuration);<br />

threadId = AppDomain.GetCurrentThreadId();<br />

return "MyCallTime was " + callDuration.ToString();<br />

}<br />

}<br />

// The delegate must have the same signature as the method<br />

// you want to call asynchronously.<br />

public delegate string AsyncDelegate(int callDuration, out int threadId);<br />

using System;<br />

using System.Threading;<br />

public class AsyncDemo {<br />

// The method to be executed asynchronously.<br />

//<br />

public string TestMethod(int callDuration, out int threadId) {<br />

Console.WriteLine("Test method begins.");<br />

Thread.Sleep(callDuration);<br />

threadId = AppDomain.GetCurrentThreadId();<br />

return "MyCallTime was " + callDuration.ToString();<br />

}<br />

}<br />

// The delegate must have the same signature as the method<br />

// you want to call asynchronously.<br />

public delegate string AsyncDelegate(int callDuration, out int threadId);


使 用 EndInvoke 等 待 异 步 调 用<br />

异 步 执 行 方 法 的 最 简 单 方 式 是 以 BeginInvoke 开 始 , 对 主 线 程 执 行 一 些 操 作 , 然 后 调 用 EndInvoke。<br />

EndInvoke 直 到 异 步 调 用 完 成 后 才 返 回 。 这 种 技 术 非 常 适 合 文 件 或 网 络 操 作 , 但 是 由 于 它 阻 塞 EndInvoke,<br />

所 以 不 要 从 用 户 界 面 的 服 务 线 程 中 使 用 它 。<br />

public class AsyncMain {<br />

static void Main(string[] args) {<br />

// The asynchronous method puts the thread id here.<br />

int threadId;<br />

// Create an instance of the test class.<br />

AsyncDemo ad = new AsyncDemo();<br />

// Create the delegate.<br />

AsyncDelegate dlgt = new AsyncDelegate(ad.TestMethod);<br />

// Initiate the asychronous call.<br />

IAsyncResult ar = dlgt.BeginInvoke(3000,<br />

out threadId, null, null);<br />

Thread.Sleep(0);<br />

Console.WriteLine("Main thread {0} does some work.",<br />

AppDomain.GetCurrentThreadId());<br />

// Call EndInvoke to Wait for the asynchronous call to complete,<br />

// and to retrieve the results.<br />

string ret = dlgt.EndInvoke(out threadId, ar);<br />

}<br />

}<br />

Console.WriteLine("The call executed on thread {0}, with return value \"{1}\".", threadId, ret);


使 用 WaitHandle 等 待 异 步 调 用<br />

等 待 WaitHandle 是 一 项 常 用 的 线 程 同 步 技 术 。 您 可 以 使 用 由 BeginInvoke 返 回 的 IAsyncResult 的<br />

AsyncWaitHandle 属 性 来 获 取 WaitHandle。 异 步 调 用 完 成 时 会 发 出 WaitHandle 信 号 , 而 您 可 以 通 过 调 用<br />

它 的 WaitOne 等 待 它 。<br />

如 果 您 使 用 WaitHandle, 则 在 异 步 调 用 完 成 之 后 , 但 在 通 过 调 用 EndInvoke 检 索 结 果 之 前 , 可 以 执 行 其<br />

他 处 理 。<br />

public class AsyncMain {<br />

static void Main(string[] args) {<br />

// The asynchronous method puts the thread id here.<br />

int threadId;<br />

// Create an instance of the test class.<br />

AsyncDemo ad = new AsyncDemo();<br />

// Create the delegate.<br />

AsyncDelegate dlgt = new AsyncDelegate(ad.TestMethod);<br />

// Initiate the asychronous call.<br />

IAsyncResult ar = dlgt.BeginInvoke(3000,<br />

out threadId, null, null);<br />

Thread.Sleep(0);<br />

Console.WriteLine("Main thread {0} does some work.",<br />

AppDomain.GetCurrentThreadId());<br />

// Wait for the WaitHandle to become signaled.<br />

ar.AsyncWaitHandle.WaitOne();<br />

// Perform additional processing here.<br />

// Call EndInvoke to retrieve the results.<br />

string ret = dlgt.EndInvoke(out threadId, ar);<br />

}<br />

}<br />

Console.WriteLine("The call executed on thread {0}, with return value \"{1}\".", threadId, ret);


轮 询 异 步 调 用 完 成<br />

您 可 以 使 用 由 BeginInvoke 返 回 的 IAsyncResult 的 IsCompleted 属 性 来 发 现 异 步 调 用 何 时 完 成 。 从 用<br />

户 界 面 的 服 务 线 程 中 进 行 异 步 调 用 时 可 以 执 行 此 操 作 。 轮 询 完 成 允 许 用 户 界 面 线 程 继 续 处 理 用 户 输 入 。<br />

public class AsyncMain {<br />

static void Main(string[] args) {<br />

// The asynchronous method puts the thread id here.<br />

int threadId;<br />

// Create an instance of the test class.<br />

AsyncDemo ad = new AsyncDemo();<br />

// Create the delegate.<br />

AsyncDelegate dlgt = new AsyncDelegate(ad.TestMethod);<br />

// Initiate the asychronous call.<br />

IAsyncResult ar = dlgt.BeginInvoke(3000,<br />

out threadId, null, null);<br />

// Poll while simulating work.<br />

while(ar.IsCompleted == false) {<br />

Thread.Sleep(10);<br />

}<br />

// Call EndInvoke to retrieve the results.<br />

string ret = dlgt.EndInvoke(out threadId, ar);<br />

}<br />

}<br />

Console.WriteLine("The call executed on thread {0}, with return value \"{1}\".", threadId, ret);<br />

异 步 调 用 完 成 时 执 行 回 调 方 法<br />

如 果 启 动 异 步 调 用 的 线 程 不 需 要 处 理 调 用 结 果 , 则 可 以 在 调 用 完 成 时 执 行 回 调 方 法 。 回 调 方 法 在<br />

ThreadPool 线 程 上 执 行 。<br />

要 使 用 回 调 方 法 , 必 须 将 代 表 该 方 法 的 AsyncCallback 委 托 传 递 给 BeginInvoke。 也 可 以 传 递 包 含 回 调 方<br />

法 将 要 使 用 的 信 息 的 对 象 。 例 如 , 可 以 传 递 启 动 调 用 时 曾 使 用 的 委 托 , 以 便 回 调 方 法 能 够 调 用 EndInvoke。


public class AsyncMain {<br />

// Asynchronous method puts the thread id here.<br />

private static int threadId;<br />

static void Main(string[] args) {<br />

// Create an instance of the test class.<br />

AsyncDemo ad = new AsyncDemo();<br />

// Create the delegate.<br />

AsyncDelegate dlgt = new AsyncDelegate(ad.TestMethod);<br />

// Initiate the asychronous call. Include an AsyncCallback<br />

// delegate representing the callback method, and the data<br />

// needed to call EndInvoke.<br />

IAsyncResult ar = dlgt.BeginInvoke(3000,<br />

out threadId,<br />

new AsyncCallback(CallbackMethod),<br />

dlgt );<br />

}<br />

Console.WriteLine("Press Enter to close application.");<br />

Console.ReadLine();<br />

// Callback method must have the same signature as the<br />

// AsyncCallback delegate.<br />

static void CallbackMethod(IAsyncResult ar) {<br />

// Retrieve the delegate.<br />

AsyncDelegate dlgt = (AsyncDelegate) ar.AsyncState;<br />

// Call EndInvoke to retrieve the results.<br />

string ret = dlgt.EndInvoke(out threadId, ar);<br />

}<br />

}<br />

Console.WriteLine("The call executed on thread {0}, with return value \"{1}\".", threadId, ret);


133H<br />

C# 的 多 线 程 机 制 探 索<br />

注 : 本 文 中 出 现 的 代 码 均 在 .net Framework RC3 环 境 中 运 行 通 过<br />

一 . 多 线 程 的 概 念<br />

Windows 是 一 个 多 任 务 的 系 统 , 如 果 你 使 用 的 是 windows 2000 及 其 以 上 版 本 , 你 可 以 通 过<br />

任 务 管 理 器 查 看 当 前 系 统 运 行 的 程 序 和 进 程 。 什 么 是 进 程 呢 ? 当 一 个 程 序 开 始 运 行 时 , 它 就 是 一 个<br />

进 程 , 进 程 所 指 包 括 运 行 中 的 程 序 和 程 序 所 使 用 到 的 内 存 和 系 统 资 源 。 而 一 个 进 程 又 是 由 多 个 线<br />

程 所 组 成 的 , 线 程 是 程 序 中 的 一 个 执 行 流 , 每 个 线 程 都 有 自 己 的 专 有 寄 存 器 ( 栈 指 针 、 程 序 计 数 器 等 ),<br />

但 代 码 区 是 共 享 的 , 即 不 同 的 线 程 可 以 执 行 同 样 的 函 数 。 多 线 程 是 指 程 序 中 包 含 多 个 执 行 流 , 即 在<br />

一 个 程 序 中 可 以 同 时 运 行 多 个 不 同 的 线 程 来 执 行 不 同 的 任 务 , 也 就 是 说 允 许 单 个 程 序 创 建 多 个 并 行<br />

执 行 的 线 程 来 完 成 各 自 的 任 务 。 浏 览 器 就 是 一 个 很 好 的 多 线 程 的 例 子 , 在 浏 览 器 中 你 可 以 在 下 载<br />

JAVA 小 应 用 程 序 或 图 象 的 同 时 滚 动 页 面 , 在 访 问 新 页 面 时 , 播 放 动 画 和 声 音 , 打 印 文 件 等 。<br />

多 线 程 的 好 处 在 于 可 以 提 高 CPU 的 利 用 率 —— 任 何 一 个 程 序 员 都 不 希 望 自 己 的 程 序 很 多 时 候<br />

没 事 可 干 , 在 多 线 程 程 序 中 , 一 个 线 程 必 须 等 待 的 时 候 ,CPU 可 以 运 行 其 它 的 线 程 而 不 是 等 待 , 这<br />

样 就 大 大 提 高 了 程 序 的 效 率 。<br />

然 而 我 们 也 必 须 认 识 到 线 程 本 身 可 能 影 响 系 统 性 能 的 不 利 方 面 , 以 正 确 使 用 线 程 :<br />

• 线 程 也 是 程 序 , 所 以 线 程 需 要 占 用 内 存 , 线 程 越 多 占 用 内 存 也 越 多<br />

• 多 线 程 需 要 协 调 和 管 理 , 所 以 需 要 CPU 时 间 跟 踪 线 程<br />

• 线 程 之 间 对 共 享 资 源 的 访 问 会 相 互 影 响 , 必 须 解 决 竞 用 共 享 资 源 的 问 题<br />

• 线 程 太 多 会 导 致 控 制 太 复 杂 , 最 终 可 能 造 成 很 多 Bug<br />

基 于 以 上 认 识 , 我 们 可 以 一 个 比 喻 来 加 深 理 解 。 假 设 有 一 个 公 司 , 公 司 里 有 很 多 各 司 其 职 的<br />

职 员 , 那 么 我 们 可 以 认 为 这 个 正 常 运 作 的 公 司 就 是 一 个 进 程 , 而 公 司 里 的 职 员 就 是 线 程 。 一 个 公<br />

司 至 少 得 有 一 个 职 员 吧 , 同 理 , 一 个 进 程 至 少 包 含 一 个 线 程 。 在 公 司 里 , 你 可 以 一 个 职 员 干 所 有 的<br />

事 , 但 是 效 率 很 显 然 是 高 不 起 来 的 , 一 个 人 的 公 司 也 不 可 能 做 大 ; 一 个 程 序 中 也 可 以 只 用 一 个 线<br />

程 去 做 事 , 事 实 上 , 一 些 过 时 的 语 言 如 fortune,basic 都 是 如 此 , 但 是 象 一 个 人 的 公 司 一 样 , 效 率 很<br />

低 , 如 果 做 大 程 序 , 效 率 更 低 —— 事 实 上 现 在 几 乎 没 有 单 线 程 的 商 业 软 件 。 公 司 的 职 员 越 多 , 老 板<br />

就 得 发 越 多 的 薪 水 给 他 们 , 还 得 耗 费 大 量 精 力 去 管 理 他 们 , 协 调 他 们 之 间 的 矛 盾 和 利 益 ; 程 序 也 是<br />

如 此 , 线 程 越 多 耗 费 的 资 源 也 越 多 , 需 要 CPU 时 间 去 跟 踪 线 程 , 还 得 解 决 诸 如 死 锁 , 同 步 等 问 题 。<br />

总 之 , 如 果 你 不 想 你 的 公 司 被 称 为 “ 皮 包 公 司 ”, 你 就 得 多 几 个 员 工 ; 如 果 你 不 想 让 你 的 程 序 显 得 稚<br />

气 , 就 在 你 的 程 序 里 引 入 多 线 程 吧 !<br />

本 文 将 对 C# 编 程 中 的 多 线 程 机 制 进 行 探 讨 , 通 过 一 些 实 例 解 决 对 线 程 的 控 制 , 多 线 程 间 通 讯<br />

等 问 题 。 为 了 省 去 创 建 GUI 那 些 繁 琐 的 步 骤 , 更 清 晰 地 逼 近 线 程 的 本 质 , 下 面 所 有 的 程 序 都 是 控 制<br />

台 程 序 , 程 序 最 后 的 Console.ReadLine() 是 为 了 使 程 序 中 途 停 下 来 , 以 便 看 清 楚 执 行 过 程 中 的 输 出 。


好 了 , 废 话 少 说 , 让 我 们 来 体 验 一 下 多 线 程 的 C# 吧 !<br />

二 . 操 纵 一 个 线 程<br />

任 何 程 序 在 执 行 时 , 至 少 有 一 个 主 线 程 , 下 面 这 段 小 程 序 可 以 给 读 者 一 个 直 观 的 印 象 :<br />

//SystemThread.cs<br />

using System;<br />

using System.Threading;<br />

namespace ThreadTest<br />

{<br />

class RunIt<br />

{<br />

[STAThread]<br />

static void Main(string[] args)<br />

{<br />

Thread.CurrentThread.Name="System Thread";// 给 当 前 线 程 起 名 为 "System<br />

Thread"<br />

Console.WriteLine(Thread.CurrentThread.Name+"'Status:"+Thread.CurrentThread.ThreadState);<br />

Console.ReadLine();<br />

}<br />

}<br />

}<br />

编 译 执 行 后 你 看 到 了 什 么 ? 是 的 , 程 序 将 产 生 如 下 输 出 :<br />

System Thread's Status:Running<br />

在 这 里 , 我 们 通 过 Thread 类 的 静 态 属 性 CurrentThread 获 取 了 当 前 执 行 的 线 程 , 对 其 Name<br />

属 性 赋 值 “System Thread”, 最 后 还 输 出 了 它 的 当 前 状 态 (ThreadState)。 所 谓 静 态 属 性 , 就 是 这<br />

个 类 所 有 对 象 所 公 有 的 属 性 , 不 管 你 创 建 了 多 少 个 这 个 类 的 实 例 , 但 是 类 的 静 态 属 性 在 内 存 中 只 有<br />

一 个 。 很 容 易 理 解 CurrentThread 为 什 么 是 静 态 的 —— 虽 然 有 多 个 线 程 同 时 存 在 , 但 是 在 某 一 个 时<br />

刻 ,CPU 只 能 执 行 其 中 一 个 。<br />

就 像 上 面 程 序 所 演 示 的 , 我 们 通 过 Thread 类 来 创 建 和 控 制 线 程 。 注 意 到 程 序 的 头 部 , 我 们 使<br />

用 了 如 下 命 名 空 间 :<br />

using System;<br />

using System.Threading;


在 .net framework class library 中 , 所 有 与 多 线 程 机 制 应 用 相 关 的 类 都 是 放 在<br />

System.Threading 命 名 空 间 中 的 。 其 中 提 供 Thread 类 用 于 创 建 线 程 ,ThreadPool 类 用 于 管 理 线<br />

程 池 等 等 , 此 外 还 提 供 解 决 了 线 程 执 行 安 排 , 死 锁 , 线 程 间 通 讯 等 实 际 问 题 的 机 制 。 如 果 你 想 在 你<br />

的 应 用 程 序 中 使 用 多 线 程 , 就 必 须 包 含 这 个 类 。Thread 类 有 几 个 至 关 重 要 的 方 法 , 描 述 如 下 :<br />

• Start(): 启 动 线 程<br />

• Sleep(int): 静 态 方 法 , 暂 停 当 前 线 程 指 定 的 毫 秒 数<br />

• Abort(): 通 常 使 用 该 方 法 来 终 止 一 个 线 程<br />

• Suspend(): 该 方 法 并 不 终 止 未 完 成 的 线 程 , 它 仅 仅 挂 起 线 程 , 以 后 还 可 恢 复 。<br />

• Resume(): 恢 复 被 Suspend() 方 法 挂 起 的 线 程 的 执 行<br />

1<br />

下 面 我 们 就 动 手 来 创 建 一 个 线 程 , 使 用 Thread 类 创 建 线 程 时 , 只 需 提 供 线 程 入 口 即 可 。<br />

线 程 入 口 使 程 序 知 道 该 让 这 个 线 程 干 什 么 事 , 在 C# 中 , 线 程 入 口 是 通 过 ThreadStart<br />

代 理 (delegate) 来 提 供 的 , 你 可 以 把 ThreadStart 理 解 为 一 个 函 数 指 针 , 指 向 线 程 要<br />

执 行 的 函 数 , 当 调 用 Thread.Start() 方 法 后 , 线 程 就 开 始 执 行 ThreadStart 所 代 表 或 者 说<br />

指 向 的 函 数 。<br />

打 开 你 的 VS.net, 新 建 一 个 控 制 台 应 用 程 序 (Console Application), 下 面 这 些 代<br />

码 将 让 你 体 味 到 完 全 控 制 一 个 线 程 的 无 穷 乐 趣 !<br />

//ThreadTest.cs<br />

using System;<br />

using System.Threading;<br />

namespace ThreadTest<br />

{<br />

public class Alpha<br />

{<br />

public void Beta()<br />

{<br />

while (true)<br />

{<br />

Console.WriteLine("Alpha.Beta is running in its own thread.");<br />

}<br />

}<br />

};<br />

public class Simple<br />

{<br />

public static int Main()<br />

{


Console.WriteLine("Thread Start/Stop/Join Sample");<br />

Alpha.Beta. ");<br />

restarted.");<br />

}<br />

}<br />

}<br />

Alpha oAlpha = new Alpha();<br />

file:// 这 里 创 建 一 个 线 程 , 使 之 执 行 Alpha 类 的 Beta() 方 法<br />

Thread oThread = new Thread(new ThreadStart(oAlpha.Beta));<br />

oThread.Start();<br />

while (!oThread.IsAlive);<br />

Thread.Sleep(1);<br />

oThread.Abort();<br />

oThread.Join();<br />

Console.WriteLine();<br />

Console.WriteLine("Alpha.Beta has finished");<br />

try<br />

{<br />

Console.WriteLine("Try to restart the Alpha.Beta thread");<br />

oThread.Start();<br />

}<br />

catch (ThreadStateException)<br />

{<br />

Console.Write("ThreadStateException trying to restart<br />

Console.WriteLine("Expected since aborted threads cannot be<br />

Console.ReadLine();<br />

}<br />

return 0;<br />

这 段 程 序 包 含 两 个 类 Alpha 和 Simple, 在 创 建 线 程 oThread 时 我 们 用 指 向<br />

Alpha.Beta() 方 法 的 初 始 化 了 ThreadStart 代 理 (delegate) 对 象 , 当 我 们 创 建 的 线 程<br />

oThread 调 用 oThread.Start() 方 法 启 动 时 , 实 际 上 程 序 运 行 的 是 Alpha.Beta() 方 法 :<br />

Alpha oAlpha = new Alpha();<br />

Thread oThread = new Thread(new ThreadStart(oAlpha.Beta));<br />

oThread.Start();<br />

然 后 在 Main() 函 数 的 while 循 环 中 , 我 们 使 用 静 态 方 法 Thread.Sleep() 让 主 线 程 停<br />

了 1ms, 这 段 时 间 CPU 转 向 执 行 线 程 oThread。 然 后 我 们 试 图 用 Thread.Abort() 方 法<br />

终 止 线 程 oThread, 注 意 后 面 的 oThread.Join(),Thread.Join() 方 法 使 主 线 程 等 待 , 直<br />

到 oThread 线 程 结 束 。 你 可 以 给 Thread.Join() 方 法 指 定 一 个 int 型 的 参 数 作 为 等 待 的 最


长 时 间 。 之 后 , 我 们 试 图 用 Thread.Start() 方 法 重 新 启 动 线 程 oThread, 但 是 显 然 Abort()<br />

方 法 带 来 的 后 果 是 不 可 恢 复 的 终 止 线 程 , 所 以 最 后 程 序 会 抛 出 ThreadStateException<br />

异 常 。<br />

程 序 最 后 得 到 的 结 果 将 如 下 图 :<br />

在 这 里 我 们 要 注 意 的 是 其 它 线 程 都 是 依 附 于 Main() 函 数 所 在 的 线 程 的 ,Main() 函 数 是<br />

C# 程 序 的 入 口 , 起 始 线 程 可 以 称 之 为 主 线 程 , 如 果 所 有 的 前 台 线 程 都 停 止 了 , 那 么 主<br />

线 程 可 以 终 止 , 而 所 有 的 后 台 线 程 都 将 无 条 件 终 止 。 而 所 有 的 线 程 虽 然 在 微 观 上 是 串 行<br />

执 行 的 , 但 是 在 宏 观 上 你 完 全 可 以 认 为 它 们 在 并 行 执 行 。<br />

读 者 一 定 注 意 到 了 Thread.ThreadState 这 个 属 性 , 这 个 属 性 代 表 了 线 程 运 行 时 状<br />

态 , 在 不 同 的 情 况 下 有 不 同 的 值 , 于 是 我 们 有 时 候 可 以 通 过 对 该 值 的 判 断 来 设 计 程 序 流<br />

程 。ThreadState 在 各 种 情 况 下 的 可 能 取 值 如 下 :<br />

• Aborted: 线 程 已 停 止<br />

• AbortRequested: 线 程 的 Thread.Abort() 方 法 已 被 调<br />

用 , 但 是 线 程 还 未 停 止<br />

• Background: 线 程 在 后 台 执 行 , 与 属 性<br />

Thread.IsBackground 有 关


• Running: 线 程 正 在 正 常 运 行<br />

• Stopped: 线 程 已 经 被 停 止<br />

• StopRequested: 线 程 正 在 被 要 求 停 止<br />

• Suspended: 线 程 已 经 被 挂 起 ( 此 状 态 下 , 可 以 通<br />

过 调 用 Resume() 方 法 重 新 运 行 )<br />

• SuspendRequested: 线 程 正 在 要 求 被 挂 起 , 但 是 未<br />

来 得 及 响 应<br />

• Unstarted: 未 调 用 Thread.Start() 开 始 线 程 的 运 行<br />

• WaitSleepJoin: 线 程 因 为 调 用 了 Wait(),Sleep() 或<br />

Join() 等 方 法 处 于 封 锁 状 态<br />

上 面 提 到 了 Background 状 态 表 示 该 线 程 在 后 台 运 行 , 那 么 后 台 运 行 的 线 程 有 什 么<br />

特 别 的 地 方 呢 ? 其 实 后 台 线 程 跟 前 台 线 程 只 有 一 个 区 别 , 那 就 是 后 台 线 程 不 妨 碍 程 序 的<br />

终 止 。 一 旦 一 个 进 程 所 有 的 前 台 线 程 都 终 止 后 ,CLR( 通 用 语 言 运 行 环 境 ) 将 通 过 调 用<br />

任 意 一 个 存 活 中 的 后 台 进 程 的 Abort() 方 法 来 彻 底 终 止 进 程 。<br />

当 线 程 之 间 争 夺 CPU 时 间 时 ,CPU 按 照 是 线 程 的 优 先 级 给 予 服 务 的 。 在 C# 应 用<br />

程 序 中 , 用 户 可 以 设 定 5 个 不 同 的 优 先 级 , 由 高 到 低 分 别 是 Highest,AboveNormal,<br />

Normal,BelowNormal,Lowest, 在 创 建 线 程 时 如 果 不 指 定 优 先 级 , 那 么 系 统 默 认 为<br />

ThreadPriority.Normal。 给 一 个 线 程 指 定 优 先 级<br />

, 我 们 可 以 使 用 如 下 代 码 :<br />

// 设 定 优 先 级 为 最 低<br />

myThread.Priority=ThreadPriority.Lowest;<br />

通 过 设 定 线 程 的 优 先 级 , 我 们 可 以 安 排 一 些 相 对 重 要 的 线 程 优 先 执 行 , 例 如 对 用 户<br />

的 响 应 等 等 。<br />

现 在 我 们 对 怎 样 创 建 和 控 制 一 个 线 程 已 经 有 了 一 个 初 步 的 了 解 , 下 面 我 们 将 深 入 研<br />

究 线 程 实 现 中 比 较 典 型 的 的 问 题 , 并 且 探 讨 其 解 决 方 法 。<br />

三 . 线 程 的 同 步 和 通 讯 —— 生 产 者 和 消 费 者<br />

假 设 这 样 一 种 情 况 , 两 个 线 程 同 时 维 护 一 个 队 列 , 如 果 一 个 线 程 对 队 列 中 添 加 元<br />

素 , 而 另 外 一 个 线 程 从 队 列 中 取 用 元 素 , 那 么 我 们 称 添 加 元 素 的 线 程 为 生 产 者 , 称 取 用<br />

元 素 的 线 程 为 消 费 者 。 生 产 者 与 消 费 者 问 题 看 起 来 很 简 单 , 但 是 却 是 多 线 程 应 用 中 一 个<br />

必 须 解 决 的 问 题 , 它 涉 及 到 线 程 之 间 的 同 步 和 通 讯 问 题 。<br />

前 面 说 过 , 每 个 线 程 都 有 自 己 的 资 源 , 但 是 代 码 区 是 共 享 的 , 即 每 个 线 程 都 可 以 执<br />

行 相 同 的 函 数 。 但 是 多 线 程 环 境 下 , 可 能 带 来 的 问 题 就 是 几 个 线 程 同 时 执 行 一 个 函 数 ,<br />

导 致 数 据 的 混 乱 , 产 生 不 可 预 料 的 结 果 , 因 此 我 们 必 须 避 免 这 种 情 况 的 发 生 。C# 提 供<br />

了 一 个 关 键 字 lock, 它 可 以 把 一 段 代 码 定 义 为 互 斥 段 (critical section), 互 斥 段 在 一


个 时 刻 内 只 允 许 一 个 线 程 进 入 执 行 , 而 其 他 线 程 必 须 等 待 。 在 C# 中 , 关 键 字 lock 定 义<br />

如 下 :<br />

lock(expression) statement_block<br />

expression 代 表 你 希 望 跟 踪 的 对 象 , 通 常 是 对 象 引 用 。 一 般 地 , 如 果 你 想 保 护 一 个 类 的<br />

实 例 , 你 可 以 使 用 this; 如 果 你 希 望 保 护 一 个 静 态 变 量 ( 如 互 斥 代 码 段 在 一 个 静 态 方 法<br />

内 部 ), 一 般 使 用 类 名 就 可 以 了 。 而 statement_block 就 是 互 斥 段 的 代 码 , 这 段 代 码 在<br />

一 个 时 刻 内 只 可 能 被 一 个 线 程 执 行 。<br />

下 面 是 一 个 使 用 lock 关 键 字 的 典 型 例 子 , 我 将 在 注 释 里 向 大 家 说 明 lock 关 键 字 的<br />

用 法 和 用 途 :


lock.cs<br />

using System;<br />

using System.Threading;<br />

internal class Account<br />

{<br />

int balance;<br />

Random r = new Random();<br />

internal Account(int initial)<br />

{<br />

balance = initial;<br />

}<br />

internal int Withdraw(int amount)<br />

{<br />

if (balance < 0)<br />

{<br />

file:// 如 果 balance 小 于 0 则 抛 出 异 常<br />

throw new Exception("Negative Balance");<br />

}<br />

// 下 面 的 代 码 保 证 在 当 前 线 程 修 改 balance 的 值 完 成 之 前<br />

// 不 会 有 其 他 线 程 也 执 行 这 段 代 码 来 修 改 balance 的 值<br />

// 因 此 ,balance 的 值 是 不 可 能 小 于 0 的<br />

lock (this)<br />

{<br />

Console.WriteLine("Current Thread:"+Thread.CurrentThread.Name);<br />

file:// 如 果 没 有 lock 关 键 字 的 保 护 , 那 么 可 能 在 执 行 完 if 的 条 件 判 断 之 后<br />

file:// 另 外 一 个 线 程 却 执 行 了 balance=balance-amount 修 改 了 balance 的 值<br />

file:// 而 这 个 修 改 对 这 个 线 程 是 不 可 见 的 , 所 以 可 能 导 致 这 时 if 的 条 件 已 经 不 成<br />

立 了<br />

file:// 但 是 , 这 个 线 程 却 继 续 执 行 balance=balance-amount, 所 以 导 致 balance<br />

可 能 小 于 0<br />

if (balance >= amount)<br />

{<br />

Thread.Sleep(5);<br />

balance = balance - amount;<br />

return amount;<br />

}<br />

else<br />

{<br />

return 0; // transaction rejected<br />

}<br />

}<br />

}


internal void DoTransactions()<br />

{<br />

for (int i = 0; i < 100; i++)<br />

Withdraw(r.Next(-50, 100));<br />

}<br />

}<br />

internal class Test<br />

{<br />

static internal Thread[] threads = new Thread[10];<br />

public static void Main()<br />

{<br />

Account acc = new Account (0);<br />

for (int i = 0; i < 10; i++)<br />

{<br />

Thread t = new Thread(new ThreadStart(acc.DoTransactions));<br />

threads[i] = t;<br />

}<br />

for (int i = 0; i < 10; i++)<br />

threads[i].Name=i.ToString();<br />

for (int i = 0; i < 10; i++)<br />

threads[i].Start();<br />

Console.ReadLine();<br />

}<br />

}<br />

而 多 线 程 公 用 一 个 对 象 时 , 也 会 出 现 和 公 用 代 码 类 似 的 问 题 , 这 种 问 题 就 不 应 该 使<br />

用 lock 关 键 字 了 , 这 里 需 要 用 到 System.Threading 中 的 一 个 类 Monitor, 我 们 可 以 称<br />

之 为 监 视 器 ,Monitor 提 供 了 使 线 程 共 享 资 源 的 方 案 。


Monitor 类 可 以 锁 定 一 个 对 象 , 一 个 线 程 只 有 得 到 这 把 锁 才 可 以 对 该 对 象 进 行 操 作 。<br />

对 象 锁 机 制 保 证 了 在 可 能 引 起 混 乱 的 情 况 下 一 个 时 刻 只 有 一 个 线 程 可 以 访 问 这 个 对 象 。<br />

Monitor 必 须 和 一 个 具 体 的 对 象 相 关 联 , 但 是 由 于 它 是 一 个 静 态 的 类 , 所 以 不 能 使 用 它<br />

来 定 义 对 象 , 而 且 它 的 所 有 方 法 都 是 静 态 的 , 不 能 使 用 对 象 来 引 用 。 下 面 代 码 说 明 了 使<br />

用 Monitor 锁 定 一 个 对 象 的 情 形 :<br />

......<br />

Queue oQueue=new Queue();<br />

......<br />

Monitor.Enter(oQueue);<br />

......// 现 在 oQueue 对 象 只 能 被 当 前 线 程 操 纵 了<br />

Monitor.Exit(oQueue);// 释 放 锁<br />

如 上 所 示 , 当 一 个 线 程 调 用 Monitor.Enter() 方 法 锁 定 一 个 对 象 时 , 这 个 对 象 就 归 它<br />

所 有 了 , 其 它 线 程 想 要 访 问 这 个 对 象 , 只 有 等 待 它 使 用 Monitor.Exit() 方 法 释 放 锁 。 为 了<br />

保 证 线 程 最 终 都 能 释 放 锁 , 你 可 以 把 Monitor.Exit() 方 法 写 在 try-catch-finally 结 构 中 的<br />

finally 代 码 块 里 。 对 于 任 何 一 个 被 Monitor 锁 定 的 对 象 , 内 存 中 都 保 存 着 与 它 相 关 的 一<br />

些 信 息 , 其 一 是 现 在 持 有 锁 的 线 程 的 引 用 , 其 二 是 一 个 预 备 队 列 , 队 列 中 保 存 了 已 经 准<br />

备 好 获 取 锁 的 线 程 , 其 三 是 一 个 等 待 队 列 , 队 列 中 保 存 着 当 前 正 在 等 待 这 个 对 象 状 态 改<br />

变 的 队 列 的 引 用 。 当 拥 有 对 象 锁 的 线 程 准 备 释 放 锁 时 , 它 使 用 Monitor.Pulse() 方 法 通 知<br />

等 待 队 列 中 的 第 一 个 线 程 , 于 是 该 线 程 被 转 移 到 预 备 队 列 中 , 当 对 象 锁 被 释 放 时 , 在 预<br />

备 队 列 中 的 线 程 可 以 立 即 获 得 对 象 锁 。<br />

下 面 是 一 个 展 示 如 何 使 用 lock 关 键 字 和 Monitor 类 来 实 现 线 程 的 同 步 和 通 讯 的 例<br />

子 , 也 是 一 个 典 型 的 生 产 者 与 消 费 者 问 题 。 这 个 例 程 中 , 生 产 者 线 程 和 消 费 者 线 程 是 交<br />

替 进 行 的 , 生 产 者 写 入 一 个 数 , 消 费 者 立 即 读 取 并 且 显 示 , 我 将 在 注 释 中 介 绍 该 程 序 的<br />

精 要 所 在 。 用 到 的 系 统 命 名 空 间 如 下 :<br />

using System;<br />

using System.Threading;<br />

首 先 , 我 们 定 义 一 个 被 操 作 的 对 象 的 类 Cell, 在 这 个 类 里 , 有 两 个 方 法 :ReadFromCell()<br />

和 WriteToCell。 消 费 者 线 程 将 调 用 ReadFromCell() 读 取 cellContents 的 内 容 并 且 显 示<br />

出 来 , 生 产 者 进 程 将 调 用 WriteToCell() 方 法 向 cellContents 写 入 数 据 。<br />

public class Cell<br />

{<br />

int cellContents; // Cell 对 象 里 边 的 内 容<br />

bool readerFlag = false; // 状 态 标 志 , 为 true 时 可 以 读 取 , 为 false 则 正 在 写 入<br />

public int ReadFromCell( )<br />

{<br />

lock(this) // Lock 关 键 字 保 证 了 什 么 , 请 大 家 看 前 面 对 lock 的 介 绍<br />

{


if (!readerFlag)// 如 果 现 在 不 可 读 取<br />

{<br />

try<br />

{<br />

file:// 等 待 WriteToCell 方 法 中 调 用 Monitor.Pulse() 方 法<br />

Monitor.Wait(this);<br />

}<br />

catch (SynchronizationLockException e)<br />

{<br />

Console.WriteLine(e);<br />

}<br />

catch (ThreadInterruptedException e)<br />

{<br />

Console.WriteLine(e);<br />

}<br />

}<br />

Console.WriteLine("Consume: {0}",cellContents);<br />

readerFlag = false; file:// 重 置 readerFlag 标 志 , 表 示 消 费 行 为 已 经 完 成<br />

Monitor.Pulse(this); file:// 通 知 WriteToCell() 方 法 ( 该 方 法 在 另 外 一 个 线 程 中 执<br />

行 , 等 待 中 )<br />

}<br />

return cellContents;<br />

}<br />

public void WriteToCell(int n)<br />

{<br />

lock(this)<br />

{<br />

if (readerFlag)<br />

{<br />

try<br />

{<br />

Monitor.Wait(this);<br />

}<br />

catch (SynchronizationLockException e)<br />

{<br />

file:// 当 同 步 方 法 ( 指 Monitor 类 除 Enter 之 外 的 方 法 ) 在 非 同 步 的 代 码 区<br />

被 调 用<br />

Console.WriteLine(e);<br />

}<br />

catch (ThreadInterruptedException e)<br />

{<br />

file:// 当 线 程 在 等 待 状 态 的 时 候 中 止<br />

Console.WriteLine(e);


法<br />

}<br />

}<br />

}<br />

}<br />

cellContents = n;<br />

Console.WriteLine("Produce: {0}",cellContents);<br />

readerFlag = true;<br />

Monitor.Pulse(this); file:// 通 知 另 外 一 个 线 程 中 正 在 等 待 的 ReadFromCell() 方<br />

}<br />

下 面 定 义 生 产 者 CellProd 和 消 费 者 类 CellCons, 它 们 都 只 有 一 个 方 法<br />

ThreadRun(), 以 便 在 Main() 函 数 中 提 供 给 线 程 的 ThreadStart 代 理 对 象 , 作 为 线 程 的<br />

入 口 。<br />

public class CellProd<br />

{<br />

Cell cell; // 被 操 作 的 Cell 对 象<br />

int quantity = 1; // 生 产 者 生 产 次 数 , 初 始 化 为 1<br />

public CellProd(Cell box, int request)<br />

{<br />

// 构 造 函 数<br />

cell = box;<br />

quantity = request;<br />

}<br />

public void ThreadRun( )<br />

{<br />

for(int looper=1; looper


public void ThreadRun( )<br />

{<br />

int valReturned;<br />

for(int looper=1; looper


producer.Start( );<br />

consumer.Start( );<br />

}<br />

}<br />

producer.Join( );<br />

consumer.Join( );<br />

Console.ReadLine();<br />

}<br />

catch (ThreadStateException e)<br />

{<br />

file:// 当 线 程 因 为 所 处 状 态 的 原 因 而 不 能 执 行 被 请 求 的 操 作<br />

Console.WriteLine(e);<br />

result = 1;<br />

}<br />

catch (ThreadInterruptedException e)<br />

{<br />

file:// 当 线 程 在 等 待 状 态 的 时 候 中 止<br />

Console.WriteLine(e);<br />

result = 1;<br />

}<br />

// 尽 管 Main() 函 数 没 有 返 回 值 , 但 下 面 这 条 语 句 可 以 向 父 进 程 返 回 执 行 结 果<br />

Environment.ExitCode = result;<br />

大 家 可 以 看 到 , 在 上 面 的 例 程 中 , 同 步 是 通 过 等 待 Monitor.Pulse() 来 完 成 的 。 首 先 生 产<br />

者 生 产 了 一 个 值 , 而 同 一 时 刻 消 费 者 处 于 等 待 状 态 , 直 到 收 到 生 产 者 的 “ 脉 冲 (Pulse)” 通<br />

知 它 生 产 已 经 完 成 , 此 后 消 费 者 进 入 消 费 状 态 , 而 生 产 者 开 始 等 待 消 费 者 完 成 操 作 后 将<br />

调 用 Monitor.Pulese() 发 出 的 “ 脉 冲 ”。 它 的 执 行 结 果 很 简 单 :<br />

Produce: 1<br />

Consume: 1<br />

Produce: 2<br />

Consume: 2<br />

Produce: 3<br />

Consume: 3<br />

...<br />

...<br />

Produce: 20<br />

Consume: 20<br />

事 实 上 , 这 个 简 单 的 例 子 已 经 帮 助 我 们 解 决 了 多 线 程 应 用 程 序 中 可 能 出 现 的 大 问<br />

题 , 只 要 领 悟 了 解 决 线 程 间 冲 突 的 基 本 方 法 , 很 容 易 把 它 应 用 到 比 较 复 杂 的 程 序 中 去 。


四 、 线 程 池 和 定 时 器 —— 多 线 程 的 自 动 管 理<br />

在 多 线 程 的 程 序 中 , 经 常 会 出 现 两 种 情 况 。 一 种 情 况 下 , 应 用 程 序 中 的 线 程 把 大 部<br />

分 的 时 间 花 费 在 等 待 状 态 , 等 待 某 个 事 件 发 生 , 然 后 才 能 给 予 响 应 ; 而 另 外 一 种 情 况 则<br />

是 线 程 平 常 都 处 于 休 眠 状 态 , 只 是 周 期 性 地 被 唤 醒 。 在 .net framework 里 边 , 我 们 使 用<br />

ThreadPool 来 对 付 第 一 种 情 况 , 使 用 Timer 来 对 付 第 二 种 情 况 。<br />

ThreadPool 类 提 供 一 个 由 系 统 维 护 的 线 程 池 —— 可 以 看 作 一 个 线 程 的 容 器 , 该 容<br />

器 需 要 Windows 2000 以 上 版 本 的 系 统 支 持 , 因 为 其 中 某 些 方 法 调 用 了 只 有 高 版 本 的<br />

Windows 才 有 的 API 函 数 。 你 可 以 使 用 ThreadPool.QueueUserWorkItem() 方 法 将 线 程<br />

安 放 在 线 程 池 里 , 该 方 法 的 原 型 如 下 :<br />

// 将 一 个 线 程 放 进 线 程 池 , 该 线 程 的 Start() 方 法 将 调 用 WaitCallback 代 理 对 象 代 表<br />

的 函 数<br />

public static bool QueueUserWorkItem(WaitCallback);<br />

// 重 载 的 方 法 如 下 , 参 数 object 将 传 递 给 WaitCallback 所 代 表 的 方 法<br />

public static bool QueueUserWorkItem(WaitCallback, object);<br />

要 注 意 的 是 ,ThreadPool 类 也 是 一 个 静 态 类 , 你 不 能 也 不 必 要 生 成 它 的 对 象 , 而<br />

且 一 旦 使 用 该 方 法 在 线 程 池 中 添 加 了 一 个 项 目 , 那 么 该 项 目 将 是 没 有 办 法 取 消 的 。 在 这<br />

里 你 无 需 自 己 建 立 线 程 , 只 需 把 你 要 做 的 工 作 写 成 函 数 , 然 后 作 为 参 数 传 递 给<br />

ThreadPool.QueueUserWorkItem() 方 法 就 行 了 , 传 递 的 方 法 就 是 依 靠 WaitCallback 代<br />

理 对 象 , 而 线 程 的 建 立 、 管 理 、 运 行 等 等 工 作 都 是 由 系 统 自 动 完 成 的 , 你 无 须 考 虑 那 些<br />

复 杂 的 细 节 问 题 , 线 程 池 的 优 点 也 就 在 这 里 体 现 出 来 了 , 就 好 像 你 是 公 司 老 板 —— 只 需<br />

要 安 排 工 作 , 而 不 必 亲 自 动 手 。<br />

下 面 的 例 程 演 示 了 ThreadPool 的 用 法 。 首 先 程 序 创 建 了 一 个 ManualResetEvent 对 象 ,<br />

该 对 象 就 像 一 个 信 号 灯 , 可 以 利 用 它 的 信 号 来 通 知 其 它 线 程 , 本 例 中 当 线 程 池 中 所 有 线<br />

程 工 作 都 完 成 以 后 ,ManualResetEvent 的 对 象 将 被 设 置 为 有 信 号 , 从 而 通 知 主 线 程 继<br />

续 运 行 。 它 有 几 个 重 要 的 方 法 :Reset(),Set(),WaitOne()。 初 始 化 该 对 象 时 , 用 户 可<br />

以 指 定 其 默 认 的 状 态 ( 有 信 号 / 无 信 号 ), 在 初 始 化 以 后 , 该 对 象 将 保 持 原 来 的 状 态 不<br />

变 直 到 它 的 Reset() 或 者 Set() 方 法 被 调 用 ,Reset() 方 法 将 其 设 置 为 无 信 号 状 态 ,Set()<br />

方 法 将 其 设 置 为 有 信 号 状 态 。WaitOne() 方 法 使 当 前 线 程 挂 起 直 到 ManualResetEvent<br />

对 象 处 于 有 信 号 状 态 , 此 时 该 线 程 将 被 激 活 。 然 后 , 程 序 将 向 线 程 池 中 添 加 工 作 项 , 这<br />

些 以 函 数 形 式 提 供 的 工 作 项 被 系 统 用 来 初 始 化 自 动 建 立 的 线 程 。 当 所 有 的 线 程 都 运 行 完<br />

了 以 后 ,ManualResetEvent.Set() 方 法 被 调 用 , 因 为 调 用 了<br />

ManualResetEvent.WaitOne() 方 法 而 处 在 等 待 状 态 的 主 线 程 将 接 收 到 这 个 信 号 , 于 是<br />

它 接 着 往 下 执 行 , 完 成 后 边 的 工 作 。<br />

using System;<br />

using System.Collections;<br />

using System.Threading;<br />

// 这 是 用 来 保 存 信 息 的 数 据 结 构 , 将 作 为 参 数 被 传 递


public class SomeState<br />

{<br />

public int Cookie;<br />

public SomeState(int iCookie)<br />

{<br />

Cookie = iCookie;<br />

}<br />

}<br />

public class Alpha<br />

{<br />

public Hashtable HashCount;<br />

public ManualResetEvent eventX;<br />

public static int iCount = 0;<br />

public static int iMaxCount = 0;<br />

public Alpha(int MaxCount)<br />

{<br />

HashCount = new Hashtable(MaxCount);<br />

iMaxCount = MaxCount;<br />

}<br />

file:// 线 程 池 里 的 线 程 将 调 用 Beta() 方 法<br />

public void Beta(Object state)<br />

{<br />

// 输 出 当 前 线 程 的 hash 编 码 值 和 Cookie 的 值<br />

Console.WriteLine(" {0} {1} :", Thread.CurrentThread.GetHashCode(),<br />

((SomeState)state).Cookie);<br />

Console.WriteLine("HashCount.Count=={0},<br />

Thread.CurrentThread.GetHashCode()=={1}", HashCount.Count,<br />

Thread.CurrentThread.GetHashCode());<br />

lock (HashCount)<br />

{<br />

file:// 如 果 当 前 的 Hash 表 中 没 有 当 前 线 程 的 Hash 值 , 则 添 加 之<br />

if (!HashCount.ContainsKey(Thread.CurrentThread.GetHashCode()))<br />

HashCount.Add (Thread.CurrentThread.GetHashCode(), 0);<br />

HashCount[Thread.CurrentThread.GetHashCode()] =<br />

((int)HashCount[Thread.CurrentThread.GetHashCode()])+1;<br />

}<br />

int iX = 2000;<br />

Thread.Sleep(iX);<br />

//Interlocked.Increment() 操 作 是 一 个 原 子 操 作 , 具 体 请 看 下 面 说 明<br />

Interlocked.Increment(ref iCount);<br />

if (iCount == iMaxCount)


}<br />

}<br />

{<br />

Console.WriteLine();<br />

Console.WriteLine("Setting eventX ");<br />

eventX.Set();<br />

}<br />

public class SimplePool<br />

{<br />

public static int Main(string[] args)<br />

{<br />

Console.WriteLine("Thread Pool Sample:");<br />

bool W2K = false;<br />

int MaxCount = 10;// 允 许 线 程 池 中 运 行 最 多 10 个 线 程<br />

// 新 建 ManualResetEvent 对 象 并 且 初 始 化 为 无 信 号 状 态<br />

ManualResetEvent eventX = new ManualResetEvent(false);<br />

Console.WriteLine("Queuing {0} items to Thread Pool", MaxCount);<br />

Alpha oAlpha = new Alpha(MaxCount); file:// 创 建 工 作 项<br />

// 注 意 初 始 化 oAlpha 对 象 的 eventX 属 性<br />

oAlpha.eventX = eventX;<br />

Console.WriteLine("Queue to Thread Pool 0");<br />

try<br />

{<br />

file:// 将 工 作 项 装 入 线 程 池<br />

file:// 这 里 要 用 到 Windows 2000 以 上 版 本 才 有 的 API, 所 以 可 能 出 现<br />

NotSupportException 异 常<br />

ThreadPool.QueueUserWorkItem(new WaitCallback(oAlpha.Beta),<br />

new SomeState(0));<br />

W2K = true;<br />

}<br />

catch (NotSupportedException)<br />

{<br />

Console.WriteLine("These API's may fail when called on a non-Windows<br />

2000 system.");<br />

W2K = false;<br />

}<br />

if (W2K)// 如 果 当 前 系 统 支 持 ThreadPool 的 方 法 .<br />

{<br />

for (int iItem=1;iItem < MaxCount;iItem++)<br />

{<br />

// 插 入 队 列 元 素<br />

Console.WriteLine("Queue to Thread Pool {0}", iItem);<br />

ThreadPool.QueueUserWorkItem(new WaitCallback(oAlpha.Beta),new


SomeState(iItem));<br />

}<br />

Console.WriteLine("Waiting for Thread Pool to drain");<br />

file:// 等 待 事 件 的 完 成 , 即 线 程 调 用 ManualResetEvent.Set() 方 法<br />

eventX.WaitOne(Timeout.Infinite,true);<br />

file://WaitOne() 方 法 使 调 用 它 的 线 程 等 待 直 到 eventX.Set() 方 法 被 调 用<br />

Console.WriteLine("Thread Pool has been drained (Event fired)");<br />

Console.WriteLine();<br />

Console.WriteLine("Load across threads");<br />

foreach(object o in oAlpha.HashCount.Keys)<br />

Console.WriteLine("{0} {1}", o, oAlpha.HashCount[o]);<br />

}<br />

Console.ReadLine();<br />

return 0;<br />

}<br />

}<br />

程 序 中 有 些 小 地 方 应 该 引 起 我 们 的 注 意 。SomeState 类 是 一 个 保 存 信 息 的 数 据 结<br />

构 , 在 上 面 的 程 序 中 , 它 作 为 参 数 被 传 递 给 每 一 个 线 程 , 你 很 容 易 就 能 理 解 这 个 , 因 为<br />

你 需 要 把 一 些 有 用 的 信 息 封 装 起 来 提 供 给 线 程 , 而 这 种 方 式 是 非 常 有 效 的 。 程 序 出 现 的<br />

InterLocked 类 也 是 专 为 多 线 程 程 序 而 存 在 的 , 它 提 供 了 一 些 有 用 的 原 子 操 作 , 所 谓 原<br />

子 操 作 就 是 在 多 线 程 程 序 中 , 如 果 这 个 线 程 调 用 这 个 操 作 修 改 一 个 变 量 , 那 么 其 他 线 程<br />

就 不 能 修 改 这 个 变 量 了 , 这 跟 lock 关 键 字 在 本 质 上 是 一 样 的 。<br />

我 们 应 该 彻 底 地 分 析 上 面 的 程 序 , 把 握 住 线 程 池 的 本 质 , 理 解 它 存 在 的 意 义 是 什 么 ,<br />

这 样 我 们 才 能 得 心 应 手 地 使 用 它 。 下 面 是 该 程 序 的 输 出 结 果 :<br />

Thread Pool Sample:<br />

Queuing 10 items to Thread Pool<br />

Queue to Thread Pool 0<br />

Queue to Thread Pool 1<br />

...<br />

...<br />

Queue to Thread Pool 9<br />

Waiting for Thread Pool to drain<br />

98 0 :<br />

HashCount.Count==0, Thread.CurrentThread.GetHashCode()==98<br />

100 1 :<br />

HashCount.Count==1, Thread.CurrentThread.GetHashCode()==100<br />

98 2 :<br />

...<br />

...


Setting eventX<br />

Thread Pool has been drained (Event fired)<br />

Load across threads<br />

101 2<br />

100 3<br />

98 4<br />

102 1<br />

与 ThreadPool 类 不 同 ,Timer 类 的 作 用 是 设 置 一 个 定 时 器 , 定 时 执 行 用 户 指 定 的<br />

函 数 , 而 这 个 函 数 的 传 递 是 靠 另 外 一 个 代 理 对 象 TimerCallback, 它 必 须 在 创 建 Timer<br />

对 象 时 就 指 定 , 并 且 不 能 更 改 。 定 时 器 启 动 后 , 系 统 将 自 动 建 立 一 个 新 的 线 程 , 并 且 在<br />

这 个 线 程 里 执 行 用 户 指 定 的 函 数 。 下 面 的 语 句 初 始 化 了 一 个 Timer 对 象 :<br />

Timer timer = new Timer(timerDelegate, s,1000, 1000);<br />

第 一 个 参 数 指 定 了 TimerCallback 代 理 对 象 ; 第 二 个 参 数 的 意 义 跟 上 面 提 到 的<br />

WaitCallback 代 理 对 象 的 一 样 , 作 为 一 个 传 递 数 据 的 对 象 传 递 给 要 调 用 的 方 法 ; 第 三 个<br />

参 数 是 延 迟 时 间 —— 计 时 开 始 的 时 刻 距 现 在 的 时 间 , 单 位 是 毫 秒 ; 第 四 个 参 数 是 定 时 器<br />

的 时 间 间 隔 —— 计 时 开 始 以 后 , 每 隔 这 么 长 的 一 段 时 间 ,TimerCallback 所 代 表 的 方 法<br />

将 被 调 用 一 次 , 单 位 也 是 毫 秒 。 这 句 话 的 意 思 就 是 将 定 时 器 的 延 迟 时 间 和 时 间 间 隔 都 设<br />

为 1 秒 钟 。<br />

定 时 器 的 设 置 是 可 以 改 变 的 , 只 要 调 用 Timer.Change() 方 法 , 这 是 一 个 参 数 类 型 重<br />

载 的 方 法 , 一 般 使 用 的 原 型 如 下 :<br />

public bool Change(long, long);<br />

下 面 这 段 代 码 将 前 边 设 置 的 定 时 器 修 改 了 一 下 :<br />

timer.Change(10000,2000);<br />

很 显 然 , 定 时 器 timer 的 时 间 间 隔 被 重 新 设 置 为 2 秒 , 停 止 计 时 10 秒 后 生 效 。<br />

下 面 这 段 程 序 演 示 了 Timer 类 的 用 法 。<br />

using System;<br />

using System.Threading;<br />

class TimerExampleState


{<br />

public int counter = 0;<br />

public Timer tmr;<br />

}<br />

class App<br />

{<br />

public static void Main()<br />

{<br />

TimerExampleState s = new TimerExampleState();<br />

// 创 建 代 理 对 象 TimerCallback, 该 代 理 将 被 定 时 调 用<br />

TimerCallback timerDelegate = new TimerCallback(CheckStatus);<br />

// 创 建 一 个 时 间 间 隔 为 1s 的 定 时 器<br />

Timer timer = new Timer(timerDelegate, s,1000, 1000);<br />

s.tmr = timer;<br />

// 主 线 程 停 下 来 等 待 Timer 对 象 的 终 止<br />

while(s.tmr != null)<br />

Thread.Sleep(0);<br />

Console.WriteLine("Timer example done.");<br />

Console.ReadLine();<br />

}<br />

file:// 下 面 是 被 定 时 调 用 的 方 法<br />

static void CheckStatus(Object state)<br />

{<br />

TimerExampleState s =(TimerExampleState)state;<br />

s.counter++;<br />

Console.WriteLine("{0} Checking Status {1}.",DateTime.Now.TimeOfDay,<br />

s.counter);<br />

if(s.counter == 5)<br />

{<br />

file:// 使 用 Change 方 法 改 变 了 时 间 间 隔<br />

(s.tmr).Change(10000,2000);<br />

Console.WriteLine("changed...");<br />

}<br />

if(s.counter == 10)<br />

{<br />

Console.WriteLine("disposing of timer...");<br />

s.tmr.Dispose();<br />

s.tmr = null;<br />

}


}<br />

}<br />

程 序 首 先 创 建 了 一 个 定 时 器 , 它 将 在 创 建 1 秒 之 后 开 始 每 隔 1 秒 调 用 一 次<br />

CheckStatus() 方 法 , 当 调 用 5 次 以 后 , 在 CheckStatus() 方 法 中 修 改 了 时 间 间 隔 为 2 秒 ,<br />

并 且 指 定 在 10 秒 后 重 新 开 始 。 当 计 数 达 到 10 次 , 调 用 Timer.Dispose() 方 法 删 除 了 timer<br />

对 象 , 主 线 程 于 是 跳 出 循 环 , 终 止 程 序 。 程 序 执 行 的 结 果 如 下 :<br />

上 面 就 是 对 ThreadPool 和 Timer 两 个 类 的 简 单 介 绍 , 充 分 利 用 系 统 提 供 的 功 能 ,<br />

可 以 为 我 们 省 去 很 多 时 间 和 精 力 —— 特 别 是 对 很 容 易 出 错 的 多 线 程 程 序 。 同 时 我 们 也 可<br />

以 看 到 .net Framework 强 大 的 内 置 对 象 , 这 些 将 对 我 们 的 编 程 带 来 莫 大 的 方 便 。<br />

、 互 斥 对 象 —— 更 加 灵 活 的 同 步 方 式<br />

有 时 候 你 会 觉 得 上 面 介 绍 的 方 法 好 像 不 够 用 , 对 , 我 们 解 决 了 代 码 和 资 源 的 同 步<br />

问 题 , 解 决 了 多 线 程 自 动 化 管 理 和 定 时 触 发 的 问 题 , 但 是 如 何 控 制 多 个 线 程 相 互 之 间 的<br />

联 系 呢 ? 例 如 我 要 到 餐 厅 吃 饭 , 在 吃 饭 之 前 我 先 得 等 待 厨 师 把 饭 菜 做 好 , 之 后 我 开 始 吃<br />

饭 , 吃 完 我 还 得 付 款 , 付 款 方 式 可 以 是 现 金 , 也 可 以 是 信 用 卡 , 付 款 之 后 我 才 能 离 开 。<br />

分 析 一 下 这 个 过 程 , 我 吃 饭 可 以 看 作 是 主 线 程 , 厨 师 做 饭 又 是 一 个 线 程 , 服 务 员 用 信 用<br />

卡 收 款 和 收 现 金 可 以 看 作 另 外 两 个 线 程 , 大 家 可 以 很 清 楚 地 看 到 其 中 的 关 系 —— 我 吃<br />

饭 必 须 等 待 厨 师 做 饭 , 然 后 等 待 两 个 收 款 线 程 之 中 任 意 一 个 的 完 成 , 然 后 我 吃 饭 这 个 线<br />

程 可 以 执 行 离 开 这 个 步 骤 , 于 是 我 吃 饭 才 算 结 束 了 。 事 实 上 , 现 实 中 有 着 比 这 更 复 杂 的<br />

联 系 , 我 们 怎 样 才 能 很 好 地 控 制 它 们 而 不 产 生 冲 突 和 重 复 呢 ?<br />

这 种 情 况 下 , 我 们 需 要 用 到 互 斥 对 象 , 即 System.Threading 命 名 空 间 中 的 Mutex<br />

类 。 大 家 一 定 坐 过 出 租 车 吧 , 事 实 上 我 们 可 以 把 Mutex 看 作 一 个 出 租 车 , 那 么 乘 客 就 是


线 程 了 , 乘 客 首 先 得 等 车 , 然 后 上 车 , 最 后 下 车 , 当 一 个 乘 客 在 车 上 时 , 其 他 乘 客 就 只<br />

有 等 他 下 车 以 后 才 可 以 上 车 。 而 线 程 与 Mutex 对 象 的 关 系 也 正 是 如 此 , 线 程 使 用<br />

Mutex.WaitOne() 方 法 等 待 Mutex 对 象 被 释 放 , 如 果 它 等 待 的 Mutex 对 象 被 释 放 了 , 它<br />

就 自 动 拥 有 这 个 对 象 , 直 到 它 调 用 Mutex.ReleaseMutex() 方 法 释 放 这 个 对 象 , 而 在 此<br />

期 间 , 其 他 想 要 获 取 这 个 Mutex 对 象 的 线 程 都 只 有 等 待 。<br />

下 面 这 个 例 子 使 用 了 Mutex 对 象 来 同 步 四 个 线 程 , 主 线 程 等 待 四 个 线 程 的 结 束 , 而<br />

这 四 个 线 程 的 运 行 又 是 与 两 个 Mutex 对 象 相 关 联 的 。 其 中 还 用 到 AutoResetEvent 类 的<br />

对 象 , 如 同 上 面 提 到 的 ManualResetEvent 对 象 一 样 , 大 家 可 以 把 它 简 单 地 理 解 为 一 个<br />

信 号 灯 , 使 用 AutoResetEvent.Set() 方 法 可 以 设 置 它 为 有 信 号 状 态 , 而 使 用<br />

AutoResetEvent.Reset() 方 法 把 它 设 置 为 无 信 号 状 态 。 这 里 用 它 的 有 信 号 状 态 来 表 示 一<br />

个 线 程 的 结 束 。<br />

// Mutex.cs<br />

using System;<br />

using System.Threading;<br />

public class MutexSample<br />

{<br />

static Mutex gM1;<br />

static Mutex gM2;<br />

const int ITERS = 100;<br />

static AutoResetEvent Event1 = new AutoResetEvent(false);<br />

static AutoResetEvent Event2 = new AutoResetEvent(false);<br />

static AutoResetEvent Event3 = new AutoResetEvent(false);<br />

static AutoResetEvent Event4 = new AutoResetEvent(false);<br />

public static void Main(String[] args)<br />

{<br />

Console.WriteLine("Mutex Sample ...");<br />

// 创 建 一 个 Mutex 对 象 , 并 且 命 名 为 MyMutex<br />

gM1 = new Mutex(true,"MyMutex");<br />

// 创 建 一 个 未 命 名 的 Mutex 对 象 .<br />

gM2 = new Mutex(true);<br />

Console.WriteLine(" - Main Owns gM1 and gM2");<br />

AutoResetEvent[] evs = new AutoResetEvent[4];<br />

evs[0] = Event1; file:// 为 后 面 的 线 程 t1,t2,t3,t4 定 义 AutoResetEvent 对 象<br />

evs[1] = Event2;<br />

evs[2] = Event3;<br />

evs[3] = Event4;<br />

MutexSample tm = new MutexSample( );


放<br />

被 释 放<br />

Thread t1 = new Thread(new ThreadStart(tm.t1Start));<br />

Thread t2 = new Thread(new ThreadStart(tm.t2Start));<br />

Thread t3 = new Thread(new ThreadStart(tm.t3Start));<br />

Thread t4 = new Thread(new ThreadStart(tm.t4Start));<br />

t1.Start( );// 使 用 Mutex.WaitAll() 方 法 等 待 一 个 Mutex 数 组 中 的 对 象 全 部 被 释<br />

t2.Start( );// 使 用 Mutex.WaitOne() 方 法 等 待 gM1 的 释 放<br />

t3.Start( );// 使 用 Mutex.WaitAny() 方 法 等 待 一 个 Mutex 数 组 中 任 意 一 个 对 象<br />

t4.Start( );// 使 用 Mutex.WaitOne() 方 法 等 待 gM2 的 释 放<br />

Thread.Sleep(2000);<br />

Console.WriteLine(" - Main releases gM1");<br />

gM1.ReleaseMutex( ); file:// 线 程 t2,t3 结 束 条 件 满 足<br />

Thread.Sleep(1000);<br />

Console.WriteLine(" - Main releases gM2");<br />

gM2.ReleaseMutex( ); file:// 线 程 t1,t4 结 束 条 件 满 足<br />

}<br />

// 等 待 所 有 四 个 线 程 结 束<br />

WaitHandle.WaitAll(evs);<br />

Console.WriteLine("... Mutex Sample");<br />

Console.ReadLine();<br />

public void t1Start( )<br />

{<br />

Console.WriteLine("t1Start started, Mutex.WaitAll(Mutex[])");<br />

Mutex[] gMs = new Mutex[2];<br />

gMs[0] = gM1;// 创 建 一 个 Mutex 数 组 作 为 Mutex.WaitAll() 方 法 的 参 数<br />

gMs[1] = gM2;<br />

Mutex.WaitAll(gMs);// 等 待 gM1 和 gM2 都 被 释 放<br />

Thread.Sleep(2000);<br />

Console.WriteLine("t1Start finished, Mutex.WaitAll(Mutex[]) satisfied");<br />

Event1.Set( ); file:// 线 程 结 束 , 将 Event1 设 置 为 有 信 号 状 态<br />

}<br />

public void t2Start( )<br />

{<br />

Console.WriteLine("t2Start started, gM1.WaitOne( )");<br />

gM1.WaitOne( );// 等 待 gM1 的 释 放<br />

Console.WriteLine("t2Start finished, gM1.WaitOne( ) satisfied");<br />

Event2.Set( );// 线 程 结 束 , 将 Event2 设 置 为 有 信 号 状 态


}<br />

public void t3Start( )<br />

{<br />

Console.WriteLine("t3Start started, Mutex.WaitAny(Mutex[])");<br />

Mutex[] gMs = new Mutex[2];<br />

gMs[0] = gM1;// 创 建 一 个 Mutex 数 组 作 为 Mutex.WaitAny() 方 法 的 参 数<br />

gMs[1] = gM2;<br />

Mutex.WaitAny(gMs);// 等 待 数 组 中 任 意 一 个 Mutex 对 象 被 释 放<br />

Console.WriteLine("t3Start finished, Mutex.WaitAny(Mutex[])");<br />

Event3.Set( );// 线 程 结 束 , 将 Event3 设 置 为 有 信 号 状 态<br />

}<br />

public void t4Start( )<br />

{<br />

Console.WriteLine("t4Start started, gM2.WaitOne( )");<br />

gM2.WaitOne( );// 等 待 gM2 被 释 放<br />

Console.WriteLine("t4Start finished, gM2.WaitOne( )");<br />

Event4.Set( );// 线 程 结 束 , 将 Event4 设 置 为 有 信 号 状 态<br />

}<br />

}<br />

下 面 是 该 程 序 的 执 行 结 果 :<br />

从 执 行 结 果 可 以 很 清 楚 地 看 到 , 线 程 t2,t3 的 运 行 是 以 gM1 的 释 放 为 条 件 的 , 而 t4<br />

在 gM2 释 放 后 开 始 执 行 ,t1 则 在 gM1 和 gM2 都 被 释 放 了 之 后 才 执 行 。Main() 函 数 最 后 ,<br />

使 用 WaitHandle 等 待 所 有 的 AutoResetEvent 对 象 的 信 号 , 这 些 对 象 的 信 号 代 表 相 应<br />

线 程 的 结 束 。


六 、 小 结<br />

多 线 程 程 序 设 计 是 一 个 庞 大 的 主 题 , 而 本 文 试 图 在 .net Framework 环 境 下 , 使 用 最<br />

新 的 C# 语 言 来 描 述 多 线 程 程 序 的 概 貌 。 希 望 本 文 能 有 助 于 大 家 理 解 线 程 这 种 概 念 , 理<br />

解 多 线 程 的 用 途 , 理 解 它 的 C# 实 现 方 法 , 理 解 线 程 将 为 我 们 带 来 的 好 处 和 麻 烦 。C# 是<br />

一 种 新 的 语 言 , 因 此 它 的 线 程 机 制 也 有 许 多 独 特 的 地 方 , 希 望 大 家 能 通 过 本 文 清 楚 地 看<br />

到 这 些 , 从 而 可 以 对 线 程 进 行 更 深 入 的 理 解 和 探 索 。


134H 打 造 迅 速 响 应 的 用 户 界 面<br />

背 景<br />

最 近 抽 时 间 开 发 了 一 个 生 成 SQL 脚 本 和 执 行 脚 本 的 135H<br />

小 工 具 , 基 本 已 经 完 成 , 但 由 于 生 成 脚<br />

本 和 执 行 脚 本 相 对 而 言 是 比 较 耗 时 的 操 作 , 所 以 原 先 的 单 线 程 模 式 会 短 暂 的 冻 结 用 户 界 面 ,<br />

由 此 , 为 了 更 好 的 用 户 体 验 , 针 对 这 两 个 耗 时 的 操 作 引 进 了 多 线 程 模 式 。 我 的 目 标 是 给 这 两<br />

个 操 作 添 加 对 应 的 进 度 条 ( 与 用 户 界 面 处 在 不 同 的 Form), 显 示 目 前 的 进 度 情 况 , 及 时 把 脚<br />

本 的 执 行 情 况 反 馈 给 用 户 。 下 面 , 我 就 依 托 这 个 小 小 的 背 景 , 谈 一 下 自 己 的 见 解 吧 。<br />

需 要 做 的 工 作<br />

首 先 , 需 要 做 的 事 情 是 , 搞 清 楚 用 户 界 面 、 进 度 条 以 及 耗 时 的 操 作 之 间 的 关 系 , 可 以 用 UML<br />

的 序 列 图 表 示 , 如 下 图 所 示 :<br />

从 图 中 已 经 可 以 清 晰 地 看 到 它 们 三 者 之 间 的 关 系 , 不 过 我 还 是 要 简 单 的 叙 述 一 下 :<br />

1) 用 户 界 面 构 造 并 显 示 进 度 条 。<br />

2) 用 户 界 面 异 步 调 用 耗 时 操 作 。


3) 耗 时 操 作 需 要 及 时 地 把 执 行 情 况 反 馈 给 用 户 界 面 。<br />

4) 用 户 界 面 把 目 前 的 执 行 情 况 通 知 给 进 度 条 。<br />

5) 耗 时 操 作 完 成 时 , 通 知 用 户 界 面 。<br />

6) 用 户 界 面 销 毁 进 度 条 , 提 示 任 务 执 行 完 毕 。<br />

明 确 了 做 什 么 之 后 , 下 一 步 就 可 以 思 考 有 哪 些 方 法 可 以 帮 助 我 们 解 决 这 个 问 题 了 。<br />

有 哪 些 方 法<br />

不 管 采 取 哪 些 方 法 , 你 都 需 要 思 考 如 下 几 个 问 题 :<br />

1) 用 户 界 面 和 耗 时 操 作 如 何 异 步 调 用 ?<br />

2) 耗 时 操 作 如 果 需 要 参 数 , 那 么 如 何 传 递 参 数 ?<br />

3) 耗 时 操 作 如 何 把 当 前 的 执 行 情 况 发 送 给 用 户 界 面 ?<br />

4) 如 果 耗 时 操 作 有 返 回 值 ( 分 为 每 一 个 小 的 耗 时 操 作 的 返 回 值 和 整 个 耗 时 操 作 的 返 回<br />

值 ), 那 么 如 何 把 执 行 的 结 果 返 回 给 用 户 界 面 ?<br />

5) 用 户 界 面 如 何 把 执 行 的 情 况 反 馈 给 进 度 条 ?<br />

如 果 这 些 问 题 你 都 已 经 考 虑 清 楚 了 , 那 么 这 里 要 解 决 的 问 题 也 就 不 再 是 问 题 了 。 让 我 们 逐 个<br />

解 答 吧 。<br />

用 户 界 面 和 耗 时 操 作 如 何 异 步 调 用 ?<br />

针 对 这 个 问 题 , 我 想 解 决 办 法 有 两 种 , 一 是 自 己 利 用 多 线 程 的 相 关 类 构 建 工 作 者 线 程 , 把 耗<br />

时 操 作 分 配 给 工 作 者 线 程 , 然 后 在 UI 线 程 里 开 启 工 作 者 线 程 。 第 二 种 方 法 , 使 用 异 步 执 行<br />

委 托 , 这 是 在 UI 线 程 里 处 理 此 类 问 题 所 特 有 的 方 法 , 使 用 此 方 法 免 去 了 很 多 代 码 , 简 单 好<br />

用 。<br />

耗 时 操 作 如 果 需 要 参 数 , 那 么 如 何 传 递 参 数 ?<br />

这 个 问 题 的 答 案 就 跟 你 上 面 选 择 的 方 式 有 关 系 了 , 如 果 你 采 用 的 是 自 己 构 建 多 线 程 , 并 且 需<br />

要 传 递 给 工 作 者 线 程 一 些 参 数 , 你 可 以 采 取 的 办 法 有 :


• 通 过 耗 时 操 作 的 公 开 属 性 给 其 传 值 。<br />

• 通 过 耗 时 操 作 类 的 构 造 函 数 的 参 数 传 值 。<br />

• 借 助 于 第 三 方 给 其 传 值 , 如 : 你 可 以 把 耗 时 操 作 需 要 的 数 据 预 先 存 储 到 一 个 文 件 里 或<br />

者 数 据 库 里 。<br />

• 混 合 使 用 以 上 的 方 法 。<br />

如 果 你 采 用 的 是 异 步 执 行 委 托 的 方 式 , 除 了 以 上 的 办 法 外 , 你 还 可 以 采 取 的 办 法 有 :<br />

• 通 过 BeginInvoke 传 给 耗 时 操 作 所 需 要 的 数 据 。<br />

借 助 这 些 方 法 均 可 以 帮 助 你 顺 利 地 把 参 数 传 递 给 耗 时 操 作 , 我 想 说 的 是 , 你 的 需 求 决 定 了 你<br />

的 办 法 , 你 的 办 法 决 定 了 你 的 代 码 量 以 及 性 能 , 所 以 , 要 三 思 而 后 行 。<br />

耗 时 操 作 如 何 把 当 前 的 执 行 情 况 发 送 给 用 户 界 面 ?<br />

由 于 耗 时 操 作 与 UI 处 于 不 同 的 线 程 , 所 以 UI 不 能 直 接 得 到 耗 时 操 作 的 执 行 情 况 , 在 这 里 ,<br />

我 们 可 以 利 用 的 办 法 有 :<br />

• 利 用 耗 时 操 作 的 事 件 , 把 执 行 情 况 发 送 给 事 件 处 理 者 ( 用 户 界 面 )。<br />

• 借 助 于 第 三 方 , 把 耗 时 操 作 的 执 行 情 况 存 储 到 一 个 文 件 里 或 者 数 据 库 里 , 供 用 户 界 面<br />

获 取 。<br />

• 构 建 消 息 通 道 , 通 过 消 息 通 道 , 把 消 息 发 送 给 对 消 息 感 兴 趣 的 接 收 者 。<br />

如 何 把 执 行 的 结 果 返 回 给 用 户 界 面 ?<br />

这 个 问 题 与 上 一 问 题 的 解 决 方 式 差 不 多 , 只 不 过 , 如 果 用 户 界 面 采 用 的 是 异 步 委 托 执 行 的 方<br />

式 与 耗 时 操 作 衔 接 的 话 , 可 以 使 用 另 外 一 种 接 收 返 回 的 执 行 结 果 , 设 置 BeginInvoke 的 回 调<br />

函 数 , 然 后 在 回 调 函 数 里 调 用 EndInvoke 接 收 耗 时 操 作 返 回 的 结 果 。<br />

用 户 界 面 如 何 把 执 行 的 情 况 反 馈 给 进 度 条 ?<br />

当 用 户 界 面 得 到 执 行 的 情 况 后 , 它 需 要 把 这 些 信 息 传 给 进 度 条 , 通 过 进 度 条 , 向 用 户 展 示 目<br />

前 耗 时 操 作 的 执 行 情 况 , 那 么 , 用 户 界 面 是 如 何 把 执 行 的 情 况 反 馈 给 进 度 条 呢 ?<br />

在 这 里 , 我 们 需 要 判 断 用 户 界 面 与 进 度 条 , 是 否 处 在 同 一 个 线 程 里 ( 使 用 InvokeRequired


判 断 ), 决 定 是 采 用 Invoke 的 方 式 , 还 是 直 接 访 问 。<br />

到 这 里 , 我 们 已 经 把 所 有 面 临 的 问 题 逐 步 剖 析 了 一 遍 , 相 信 你 已 经 对 类 似 的 问 题 有 了 自 己 的<br />

解 决 办 法 。 剩 下 的 事 情 就 是 如 何 在 实 际 的 代 码 中 运 用 这 些 方 法 了 。<br />

如 何 解 决 我 的 问 题 ?<br />

接 下 来 , 针 对 我 的 脚 本 辅 助 工 具 中 遇 到 的 问 题 , 做 一 个 阐 述 。 由 于 我 的 工 具 里 有 两 个 比 较 耗<br />

时 的 操 作 , 而 解 决 它 们 的 办 法 都 较 类 似 , 所 以 , 我 就 以 脚 本 的 产 生 为 例 子 , 说 明 一 下 我 的 解<br />

决 之 道 。<br />

首 先 , 定 义 一 个 委 托 , 用 于 异 步 执 行 耗 时 操 作 即 产 生 脚 本 , 代 码 如 下 :<br />

r);<br />

private delegate string CreateSql(ArrayList list,BuildSQL.DataBase.SQLServer serve<br />

然 后 , 我 需 要 做 的 是 : 构 建 进 度 条 , 启 动 耗 时 操 作 。 在 这 里 , 我 采 用 的 是 异 步 执 行 委 托 的 方<br />

式 启 动 耗 时 操 作 的 , 而 我 的 耗 时 操 作 : 产 生 脚 本 会 在 结 束 的 时 候 返 回 产 生 的 脚 本 。 代 码 如 下 :<br />

privatevoid StartCreateSql(ArrayList list,BuildSQL.DataBase.SQLServer server)<br />

{<br />

// 禁 用 产 生 脚 本 按 钮<br />

this.btnCreateSql.Enabled = false;<br />

// 启 动 进 度 条<br />

bar = new ProgressBar();<br />

bar.Title = " 正 在 生 成 脚 本 ";<br />

bar.StartPosition = FormStartPosition.CenterParent;<br />

bar.Show();<br />

bar.ShowProgress(list.Count,0);<br />

// 开 始 产 生 脚 本


BuildSQL.DTO.SQLServer2000.SQLScript sqlScript = new BuildSQL.DTO.SQLSer<br />

ver2000.SQLScript();<br />

// 绑 定 事 件 , 获 取 耗 时 操 作 执 行 的 情 况<br />

sqlScript.ProgressChangeEventHandler+=new BuildSQL.DTO.SQLServer2000.SQ<br />

LScript.ProgressDelegate(sqlScript_ProgressChangeEventHandler);<br />

CreateSql sql = new CreateSql(sqlScript.CreateSQLScript);<br />

// 异 步 执 行 委 托 , 并 绑 定 回 调 函 数 CreateSqlFinish, 目 的 是 在 回 调 函 数 里 获 取 产 生 的 脚 本<br />

sql.BeginInvoke(list,server,new AsyncCallback(CreateSqlFinish),sql);<br />

}<br />

为 了 获 取 耗 时 操 作 的 执 行 情 况 , 我 采 用 了 事 件 机 制 , 封 装 在 耗 时 操 作 类 里 的 代 码 为 :<br />

// 定 义 进 度 的 委 托 和 事 件<br />

public delegate void ProgressDelegate(object sender,ProgressEventArgs e);<br />

public event ProgressDelegate ProgressChangeEventHandler;<br />

// 触 发 事 件<br />

public int Now<br />

{<br />

get{return m_now;}<br />

set<br />

{<br />

m_now = value;<br />

// 触 发 事 件<br />

if(ProgressChangeEventHandler != null)<br />

{<br />

ProgressEventArgs e = new ProgressEventArgs(m_max,value);<br />

ProgressChangeEventHandler(this,e);<br />

}<br />

// 任 务 完 成 事 件


if(value == m_max)<br />

{<br />

if(ProgressFinishEventHandler != null)<br />

ProgressFinishEventHandler(this, new EventArgs());<br />

}<br />

}<br />

}<br />

用 户 界 面 接 收 耗 时 操 作 的 执 行 情 况 , 并 把 这 些 信 息 发 送 给 进 度 条 , 代 码 如 下 :<br />

privatevoid sqlScript_ProgressChangeEventHandler(object sender, BuildSQL.DTO.SQLSe<br />

rver2000.ProgressEventArgs e)<br />

{<br />

if(InvokeRequired)<br />

{<br />

ShowProgressDelegate show = new ShowProgressDelegate(bar.ShowProgress);<br />

show.BeginInvoke(e.Max,e.Now,null,null);<br />

}<br />

else<br />

{<br />

bar.ShowProgress(e.Max,e.Now);<br />

}<br />

}<br />

最 后 , 当 耗 时 操 作 结 束 的 时 候 , 获 取 其 执 行 的 结 果 , 在 这 里 获 取 的 是 产 生 的 脚 本 , 如 下 :<br />

private void CreateSqlFinish(IAsyncResult result)<br />

{<br />

if(bar != null)<br />

{


ar.Close();<br />

}<br />

if(MessageBox.Show(" 脚 本 产 生 成 功 !") == DialogResult.OK)<br />

{<br />

this.txtScript.Text = ((CreateSql)result.AsyncState).EndInvoke(result);<br />

// 启 用 产 生 脚 本 按 钮<br />

this.btnCreateSql.Enabled = true;<br />

}<br />

}<br />

到 此 , 我 的 这 个 工 具 中 所 要 解 决 的 问 题 已 经 解 决 了 。 在 这 里 , 我 想 提 醒 的 是 , 你 不 必 关 注 代<br />

码 的 细 节 , 而 应 该 关 注 代 码 的 骨 架 。 当 然 这 些 代 码 如 果 想 运 用 在 其 它 地 方 , 可 能 需 要 重 新 编<br />

写 , 我 想 , 如 果 你 愿 意 , 那 么 下 一 步 考 虑 的 事 情 应 该 是 如 何 把 这 些 代 码 抽 取 到 一 个 类 中 , 更<br />

容 易 的 应 用 到 类 似 这 样 的 需 求 里 。<br />

最 后 , 给 大 家 推 荐 一 下 我 的 这 个 136H<br />

如 果 有 不 对 的 地 方 , 还 请 大 家 批 评 指 正 。<br />

脚 本 辅 助 工 具 , 希 望 它 也 可 以 给 你 的 工 作 带 来 便 利 。 文 中<br />

备 注 : 如 果 你 需 要 了 解 更 多 的 信 息 , 你 可 以 阅 读 msdn 的 相 关 文 章 :137H 通 过 多 线 程 为 基 于 .NET 的<br />

应 用 程 序 实 现 响 应 迅 速 的 用 户


多 线 程 在 Visual C# 网 络 编 程 中 的 应 用<br />

admin 发 表 于 :04-05-18<br />

网 络 应 用 程 序 的 一 般 都 会 或 多 或 少 的 使 用 到 线 程 , 甚 至 可 以 说 , 一 个 功 能 稍 微 强 大 的 网 络 应 用 程 序 总 会 在 其 中 开 出 或 多 或 少 的 线 程 , 如 果 应 用<br />

程 序 中 开 出 的 线 程 数 目 大 于 二 个 , 那 么 就 可 以 把 这 个 程 序 称 之 为 多 线 程 应 用 程 序 。 那 么 为 什 么 在 网 络 应 用 程 序 总 会 和 线 程 交 缠 在 一 起 呢 ? 这 是 因 为<br />

网 络 应 用 程 序 在 执 行 的 时 候 , 会 遇 到 很 多 意 想 不 到 的 问 题 , 其 中 最 常 见 的 是 网 络 阻 塞 和 网 络 等 待 等 。<br />

程 序 在 处 理 这 些 问 题 的 时 候 往 往 需 要 花 费 很 多 的 时 间 , 如 果 不 使 用 线 程 , 则 程 序 在 执 行 时 的 就 会 表 现 出 如 运 行 速 度 慢 , 执 行 时 间 长 , 容 易 出 现<br />

错 误 、 反 应 迟 钝 等 问 题 。 而 如 果 把 这 些 可 能 造 成 大 量 占 用 程 序 执 行 时 间 的 过 程 放 在 线 程 中 处 理 , 就 往 往 能 够 大 大 提 高 应 用 程 序 的 运 行 效 率 和 性 能 和<br />

获 得 更 优 良 的 可 伸 缩 性 。 那 么 这 是 否 就 意 味 着 应 该 在 网 络 应 用 程 序 中 广 泛 的 使 用 线 程 呢 ? 情 况 并 非 如 此 , 线 程 其 实 是 一 把 双 刃 剑 , 如 果 不 分 场 合 ,<br />

在 不 需 要 使 用 的 地 方 强 行 使 用 就 可 能 会 产 生 许 多 程 序 垃 圾 , 或 者 在 程 序 结 束 后 , 由 于 没 有 能 够 销 毁 创 建 的 进 程 而 导 致 应 用 程 序 挂 起 等 问 题 。<br />

所 以 如 果 你 认 为 自 己 编 写 的 代 码 足 够 快 , 那 我 给 你 的 建 议 还 是 别 使 用 线 程 或 多 线 程 。 这 里 要 提 醒 诸 位 的 是 如 果 您 对 在 Windows 下 的 线 程 和 其 执<br />

行 原 理 和 机 制 还 不 十 分 清 楚 , 可 以 先 参 阅 一 下 介 绍 Windows 操 作 系 统 方 面 的 书 籍 , 它 们 一 般 都 会 对 其 进 行 比 较 详 细 的 阐 述 。 然 后 再 阅 读 本 文 。<br />

一 . 简 介 在 Visual C# 中 创 建 和 使 用 线 程 :<br />

Visual C# 中 使 用 的 线 程 都 是 通 过 自 命 名 空 间 System.Threading 中 的 Thread 类 经 常 实 例 化 完 成 的 。 通 过 Thread 类 的 构 造 函 数 来 创 建 可 供<br />

Visual C# 使 用 的 线 程 , 通 过 Thread 中 的 方 法 和 属 性 来 设 定 线 程 属 性 和 控 制 线 程 的 状 态 。 以 下 Thread 类 中 的 最 典 型 的 构 造 函 数 语 法 , 在 Visual C<br />

# 中 一 般 使 用 这 个 构 造 函 数 来 创 建 、 初 始 化 Thread 实 例 。<br />

public Thread (<br />

ThreadStart start<br />

) ;<br />

参 数<br />

start ThreadStart 委 托 , 它 将 引 用 此 线 程 开 始 执 行 时 要 调 用 的 方 法 。<br />

Thread 还 提 供 了 其 他 的 构 造 函 数 来 创 建 线 程 , 这 里 就 不 一 一 介 绍 了 。 表 01 是 Thread 类 中 的 一 些 常 用 的 方 法 及 其 简 要 说 明 :<br />

方 法<br />

Abort<br />

说 明<br />

调 用 此 方 法 通 常 会 终 止 线 程 , 但 会 引 起 ThreadAbortException 类 型 异 常 。<br />

中 断 处 于 WaitSleepJoin 线 程 状 态 的 线 程 。


Interrupt<br />

Join<br />

阻 塞 调 用 线 程 , 直 到 某 个 线 程 终 止 时 为 止 。<br />

ResetAbort<br />

取 消 当 前 线 程 调 用 的 Abor 方 法 。<br />

Resume<br />

继 续 已 挂 起 的 线 程 。<br />

Sleep<br />

当 前 线 程 阻 塞 指 定 的 毫 秒 数 。<br />

Start<br />

操 作 系 统 将 当 前 实 例 的 状 态 更 改 为 ThreadState.Running。<br />

Suspend 挂 起 线 程 , 或 者 如 果 线 程 已 挂 起 , 则 不 起 作 用 。<br />

表 01:Thread 类 的 常 用 方 法 及 其 说 明<br />

这 里 要 注 意 的 是 在 .Net 中 执 行 一 个 线 程 , 当 线 程 执 行 完 毕 后 , 一 般 会 自 动 销 毁 。 如 果 线 程 没 有 自 动 销 毁 可 通 过 Thread 中 的 Abort 方 法 来 手 动<br />

销 毁 , 但 同 样 要 注 意 的 是 如 果 线 程 中 使 用 的 资 源 没 有 完 全 销 毁 ,Abort 方 法 执 行 后 , 也 不 能 保 证 线 程 被 销 毁 。 在 Thread 类 中 还 提 供 了 一 些 属 性 用 以<br />

设 定 和 获 取 创 建 的 Thread 实 例 属 性 , 表 02 中 是 Thread 类 的 一 些 常 用 属 性 及 其 说 明 :<br />

属 性<br />

说 明<br />

CurrentCulture<br />

获 取 或 设 置 当 前 线 程 的 区 域 性 。<br />

CurrentThread<br />

获 取 当 前 正 在 运 行 的 线 程 。<br />

IsAlive<br />

获 取 一 个 值 , 该 值 指 示 当 前 线 程 的 执 行 状 态 。<br />

获 取 或 设 置 一 个 值 , 该 值 指 示 某 个 线 程 是 否 为 后 台 线 程 。


IsBackground<br />

Name<br />

获 取 或 设 置 线 程 的 名 称 。<br />

Priority<br />

获 取 或 设 置 一 个 值 , 该 值 指 示 线 程 的 调 度 优 先 级 。<br />

ThreadState 获 取 一 个 值 , 该 值 包 含 当 前 线 程 的 状 态 。<br />

表 02:Thread 类 的 常 用 属 性 及 其 说 明<br />

二 . 本 文 的 主 要 内 容 及 程 序 调 试 和 运 行 环 境 :<br />

本 文 的 主 要 内 容 是 介 绍 多 线 程 给 用 Visual C# 编 写 网 络 应 用 程 序 带 来 的 更 高 性 能 提 高 。 具 体 的 做 法 是 在 Visual C# 用 二 种 不 同 的 方 法 , 一 种 采<br />

用 了 多 线 程 , 另 一 种 不 是 , 来 实 现 同 一 个 具 体 网 络 应 用 示 例 , 此 示 例 的 功 能 是 获 取 网 络 同 一 网 段 多 个 IP 地 址 对 应 的 计 算 机 的 在 线 状 态 和 对 应 的 计 算<br />

机 名 称 , 通 过 比 较 这 二 种 方 法 的 不 同 执 行 效 率 就 可 知 多 线 程 对 提 高 网 络 应 用 程 序 的 执 行 效 率 是 多 么 的 重 要 了 。 以 下 是 本 文 中 设 计 到 程 序 的 调 试 和 运<br />

行 的 基 本 环 境 配 置 :<br />

(1). 微 软 公 司 视 窗 2000 服 务 器 版 。<br />

(2).Visual Studio .Net 2002 正 式 版 ,.Net FrameWork SDK 版 本 号 3705。<br />

三 . 扫 描 网 络 计 算 机 的 原 理 :<br />

下 面 介 绍 的 这 个 示 例 的 功 能 是 通 过 扫 描 一 个 给 定 区 间 IP 地 址 , 来 判 断 这 些 IP 地 址 对 应 的 计 算 机 是 否 在 线 , 如 果 在 线 则 获 得 IP 地 址 对 应 的 计 算<br />

机 名 称 。 程 序 判 断 计 算 机 是 否 在 线 的 是 采 用 对 给 定 IP 地 址 的 计 算 机 进 行 DNS 解 析 , 如 果 能 够 根 据 IP 地 址 解 析 出 对 应 的 计 算 机 名 称 , 则 说 明 此 IP<br />

地 址 对 应 的 计 算 机 在 线 ; 反 之 , 如 果 解 析 不 出 , 则 会 产 生 异 常 出 错 , 通 过 对 异 常 的 捕 获 , 得 到 此 IP 地 址 对 应 的 计 算 机 并 不 在 线 。<br />

为 了 更 清 楚 地 说 明 问 题 和 便 于 掌 握 在 Visual C# 编 写 多 线 程 网 络 应 用 程 序 的 方 法 , 本 文 首 先 介 绍 的 是 不 基 于 多 线 程 的 网 络 计 算 机 扫 描 程 序 的 编<br />

写 步 骤 , 然 后 再 在 其 基 础 上 , 把 它 修 改 成 多 线 程 的 计 算 机 扫 描 程 序 , 最 后 比 较 这 二 个 程 序 的 执 行 效 率 , 你 就 会 发 现 线 程 在 网 络 编 程 中 的 重 要 作 用 了 。<br />

四 .Visual C# 实 现 不 基 于 多 线 程 的 网 络 计 算 机 扫 描 程 序<br />

以 下 是 在 Visual C# 实 现 不 基 于 多 线 程 的 网 络 计 算 机 扫 描 程 序 步 骤 :<br />

1. 启 动 Visual Studio .Net, 并 新 建 一 个 Visual C# 项 目 , 项 目 名 称 为 【 扫 描 网 络 计 算 机 】。<br />

2. 把 Visual Studio .Net 的 当 前 窗 口 切 换 到 【Form1.cs( 设 计 )】 窗 口 , 并 从 【 工 具 箱 】 中 的 【Windows 窗 体 组 件 】 选 项 卡 中 往 Form1 窗 体<br />

中 拖 入 下 列 组 件 , 并 执 行 相 应 操 作 :<br />

四 个 NumericUpDown 组 件 , 用 以 组 合 成 一 个 IP 地 址 区 间 。


一 个 ListBox 组 件 , 用 以 显 示 扫 描 后 的 结 果 。<br />

一 个 ProgressBar 组 件 , 用 以 显 示 程 序 的 运 行 进 度 。<br />

四 个 Label 组 件 , 用 以 显 示 提 示 信 息 。<br />

一 个 GroupBox 组 件 。<br />

一 个 Button 组 件 , 名 称 为 button1, 并 在 这 组 件 拖 入 窗 体 后 , 双 击 button1, 这 样 Visual Studio .Net 就 会 产 生 这 button1 组 件 Click 事 件 对<br />

应 的 处 理 代 码 。<br />

界 面 设 置 如 下 图 :<br />

图 01:【 扫 描 网 络 计 算 机 】 项 目 的 设 计 界 面<br />

3. 把 Visual Studio .Net 的 当 前 窗 口 切 换 到 【Form1.cs】, 进 入 Form1.cs 文 件 的 编 辑 界 面 。 在 Form1.cs 头 部 , 用 下 列 代 码 替 换 系 统 缺 省 的<br />

导 入 命 名 空 间 代 码 :<br />

using System ;<br />

using System.Drawing ;<br />

using System.Collections ;<br />

using System.ComponentModel ;<br />

using System.Windows.Forms ;<br />

using System.Data ;<br />

using System.Net.Sockets ;<br />

using System.Net ;<br />

4. 用 下 列 代 码 替 换 Form1.cs 中 的 button1 的 Click 时 间 对 应 的 处 理 代 码 , 下 列 代 码 的 功 能 是 扫 描 给 定 的 IP 地 址 区 间 , 并 把 扫 描 结 果 显 示 出 来 。<br />

private void button1_Click ( object sender , System.EventArgs e )<br />

{<br />

listBox1.Items.Clear ( ) ;<br />

// 清 楚 扫 描 结 果 显 示 区 域<br />

DateTime StartTime = DateTime.Now ;<br />

// 获 取 当 前 时 间<br />

string mask = numericUpDown1.Value.ToString ( ) + "." + numericUpDown2.Value.ToString ( ) +<br />

"." + numericUpDown3.Value.ToString ( ) + "." ;<br />

int Min = ( int ) numericUpDown4.Value ;


}<br />

int Max = ( int ) numericUpDown5.Value ;<br />

if ( Min > Max )<br />

{<br />

MessageBox.Show ( " 输 入 的 IP 地 址 区 间 不 合 法 , 请 检 查 !" , " 错 误 !" ) ;<br />

return ;<br />

}<br />

// 判 断 输 入 的 IP 地 址 区 间 是 否 合 法<br />

progressBar1.Minimum = Min ;<br />

progressBar1.Maximum = Max ;<br />

int i ;<br />

for ( i = Min ; i


图 02: 不 基 于 多 线 程 的 【 扫 描 网 络 计 算 机 】 项 目 的 运 行 界 面<br />

五 . 把 【 扫 描 网 络 计 算 机 】 程 序 修 改 成 基 于 多 线 程 的 程 序 :<br />

在 修 改 成 多 线 程 程 序 之 前 , 必 须 面 对 并 解 决 下 面 几 个 问 题 :<br />

1. 线 程 是 无 返 回 值 的 , 所 以 在 线 程 中 处 理 、 调 用 的 应 是 一 个 过 程 , 所 以 要 把 扫 描 IP 地 址 对 应 的 计 算 机 的 代 码 给 包 装 成 一 个 过 程 。<br />

2. 放 在 线 程 中 处 理 的 过 程 , 因 为 没 有 返 回 值 , 从 而 无 法 向 主 程 序 ( 进 程 ) 传 递 数 值 。 但 扫 描 IP 地 址 对 应 的 计 算 机 的 过 程 却 要 向 主 程 序 ( 进 程 )<br />

传 递 IP 地 址 是 否 在 线 的 数 据 , 所 以 在 修 改 成 多 线 程 程 序 之 前 , 必 须 从 线 程 往 主 程 序 ( 进 程 ) 传 递 数 据 的 问 题 。<br />

下 面 是 在 【 扫 描 网 络 计 算 机 】 项 目 的 基 础 上 , 把 它 修 改 成 基 于 多 线 程 程 序 的 具 体 实 现 步 骤 :<br />

1. 由 于 程 序 中 使 用 到 线 程 , 所 以 在 Form1.cs 代 码 首 部 , 导 入 命 名 空 间 代 码 区 中 加 入 下 列 代 码 , 下 列 代 码 是 导 入 Thread 类 所 在 的 命 名 空 间 。<br />

using System.Threading ;<br />

2. 在 Form1.cs 代 码 的 namespace 代 码 区 加 入 下 列 语 句 , 下 列 语 句 是 定 义 一 个 delegate:<br />

public delegate void UpdateList ( string sIP , string sHostName ) ;<br />

3. 在 Form1.cs 中 的 定 义 Form1 的 class 代 码 区 定 义 加 入 下 列 代 码 , 下 列 代 码 是 定 义 一 个 变 量 , 用 以 存 放 程 序 执 行 的 时 间 :<br />

private System.DateTime StartTime ;<br />

4. 在 Form1.cs 代 码 的 Main 函 数 之 后 , 添 加 下 列 代 码 , 下 列 代 码 是 创 建 一 个 名 称 为 ping 的 Class, 这 个 Class 能 够 通 过 其 设 定 的 属 性 接 收 给<br />

定 的 IP 地 址 字 符 串 , 并 据 此 来 判 断 此 IP 地 址 字 符 串 对 应 的 计 算 机 是 否 在 线 , 并 通 过 其 设 定 的 HostName 属 性 接 收 从 线 程 传 递 来 的 数 据 。<br />

public class ping<br />

{<br />

public UpdateList ul ;<br />

public string ip ;


}<br />

// 定 义 一 个 变 量 , 用 以 接 收 传 送 来 的 IP 地 址 字 符 串<br />

public string HostName ;<br />

// 定 义 一 个 变 量 , 用 以 向 主 进 展 传 递 对 应 IP 地 址 是 否 在 线 数 据<br />

public void scan ( )<br />

{<br />

IPAddress myIP = IPAddress.Parse ( ip ) ;<br />

try<br />

{<br />

IPHostEntry myHost = Dns.GetHostByAddress ( myIP );<br />

HostName = myHost.HostName.ToString ( ) ;<br />

}<br />

catch<br />

{<br />

HostName = "" ;<br />

}<br />

if (HostName == "")<br />

HostName = " 主 机 没 有 响 应 !";<br />

if ( ul != null)<br />

ul ( ip , HostName ) ;<br />

}<br />

// 定 义 一 个 过 程 ( 也 可 以 看 出 为 方 法 ), 用 以 判 断 传 送 来 的 IP 地 址 对 应 计 算 机 是 否 在 线<br />

5. 在 Form1.cs 中 添 加 完 上 述 代 码 后 , 再 添 加 下 列 代 码 :<br />

void UpdateMyList ( string sIP , string sHostName )<br />

{<br />

lock ( listBox1 )<br />

{<br />

listBox1.Items.Add ( sIP + " " + sHostName ) ;<br />

if ( progressBar1.Value != progressBar1.Maximum )<br />

{<br />

progressBar1.Value++ ;<br />

}<br />

if ( progressBar1.Value == progressBar1.Maximum )<br />

{<br />

MessageBox.Show ( " 成 功 完 成 检 测 !" , " 提 示 " ) ;<br />

DateTime EndTime = DateTime.Now ;<br />

TimeSpan ts = EndTime-StartTime ;


}<br />

}<br />

}<br />

label4.Text = ts.Seconds.ToString ( ) + " 秒 " ;<br />

// 显 示 扫 描 计 算 机 所 需 要 的 时 间<br />

progressBar1.Value = progressBar1.Minimum ;<br />

6. 用 下 列 代 码 替 换 Form1.cs 中 button1 的 Click 事 件 对 应 的 处 理 代 码 , 下 列 代 码 功 能 是 创 建 多 个 扫 描 给 定 IP 地 址 区 间 对 应 的 计 算 机 线 程 实 例 ,<br />

并 显 示 扫 描 结 果 。<br />

private void button1_Click(object sender, System.EventArgs e)<br />

{<br />

listBox1.Items.Clear ( ) ;<br />

// 清 楚 扫 描 结 果 显 示 区 域<br />

StartTime = DateTime.Now ;<br />

// 获 取 当 前 时 间<br />

string mask = numericUpDown1.Value.ToString ( ) + "." + numericUpDown2.Value.ToString ( ) +<br />

"." + numericUpDown3.Value.ToString ( ) + "." ;<br />

int Min = ( int ) numericUpDown4.Value ;<br />

int Max = ( int ) numericUpDown5.Value ;<br />

if ( Min > Max )<br />

{<br />

MessageBox.Show ( " 输 入 的 IP 地 址 区 间 不 合 法 , 请 检 查 !" , " 错 误 !" ) ;<br />

return ;<br />

}<br />

// 判 断 输 入 的 IP 地 址 区 间 是 否 合 法<br />

int _ThreadNum = Max - Min + 1 ;<br />

Thread[] mythread = new Thread [ _ThreadNum ] ;<br />

// 创 建 一 个 多 个 Thread 实 例<br />

progressBar1.Minimum = Min ;<br />

progressBar1.Maximum = Max + 1 ;<br />

progressBar1.Value = Min ;<br />

int i ;<br />

for (i = Min ; i


}<br />

}<br />

// 向 这 个 ping 实 例 中 传 递 IP 地 址 字 符 串<br />

mythread[k] = new Thread ( new ThreadStart ( HostPing.scan ) ) ;<br />

// 初 始 化 一 个 线 程 实 例<br />

mythread[k].Start ( ) ;<br />

// 启 动 线 程<br />

至 此 ,【 扫 描 网 络 计 算 机 】 项 目 已 经 被 修 改 成 一 个 多 线 程 的 程 序 了 , 此 时 在 运 行 程 序 , 并 且 同 样 再 扫 描 上 面 给 定 IP 地 址 区 间 对 应 的 计 算 机 , 就<br />

会 惊 奇 的 发 现 程 序 执 行 时 间 所 建 为 10 秒 了 , 并 且 不 论 要 扫 描 的 计 算 机 数 目 有 多 少 , 程 序 的 运 行 时 间 也 是 10 秒 左 右 , 这 是 因 为 程 序 为 扫 描 每 一 个 IP<br />

都 分 配 一 个 线 程 , 这 样 程 序 的 执 行 时 间 就 不 与 要 扫 描 的 IP 地 址 段 中 的 IP 地 址 数 目 有 关 联 了 , 这 样 也 就 大 大 减 少 了 程 序 的 运 行 时 间 , 提 高 了 程 序 的<br />

运 行 效 率 , 这 也 充 分 体 现 出 多 线 程 给 网 络 编 程 带 来 的 好 处 。 图 03 也 是 程 序 扫 描 "10.138.198.1" 至 "10.138.198.10" 这 个 IP 地 址 区 间 计 算 机 后 的 运 行<br />

界 面 所 示 :<br />

图 03: 基 于 多 线 程 的 【 扫 描 网 络 计 算 机 】 项 目 的 运 行 界 面<br />

通 过 对 二 个 程 序 的 比 较 可 见 , 在 编 写 网 络 应 用 程 序 中 , 正 确 的 使 用 线 程 的 确 能 够 大 大 提 高 程 序 的 运 行 效 率 。<br />

六 . 总 结 :<br />

至 此 , 本 节 要 介 绍 的 内 容 就 全 部 结 束 了 , 不 知 道 诸 位 通 过 上 面 的 介 绍 是 否 了 解 、 掌 握 了 下 面 几 点 :<br />

1. 如 何 获 取 系 统 当 前 时 间 , 和 实 现 时 间 日 期 类 型 数 据 的 加 减 。<br />

2. 在 编 写 网 络 应 用 程 序 时 候 , 使 用 线 程 ( 多 线 程 ) 的 原 因 , 以 及 线 程 ( 多 线 程 ) 会 给 网 络 应 用 程 序 带 来 什 么 好 处 。<br />

3. 如 何 在 应 用 程 序 中 创 建 多 个 线 程 实 例 。<br />

4. 如 何 实 现 带 " 返 回 值 " 的 线 程 。<br />

如 果 上 述 要 点 你 都 能 够 掌 握 , 那 是 再 好 不 过 的 了 。 但 如 果 你 对 线 程 及 其 使 用 方 法 还 感 觉 模 糊 , 那 也 不 要 紧 , 毕 竟 线 程 在 编 程 技 术 中 是 一 个 内 容<br />

丰 富 , 使 用 复 杂 的 东 东 , 要 立 马 掌 握 的 确 是 很 困 难 的 事 情 。 在 以 后 的 文 章 中 也 将 再 介 绍 这 方 面 的 内 容 。


作 者 : 维 维 出 处 : 天 极 网 责 任 编 辑 : 138H<br />

基 于 .NET 的 多 线 程 编 程 入 门<br />

方 舟 [ 2005-09-14 08:30 ]<br />

简 介<br />

多 线 程 在 构 建 大 型 系 统 的 时 候 是 需 要 重 点 关 注 的 一 个 重 要 方 面 , 特 别 是 在 效 率 ( 系 统 跑<br />

得 多 快 ?) 和 性 能 ( 系 统 工 作 正 常 ?) 之 间 做 一 个 权 衡 的 时 候 。 恰 当 的 使 用 多 线 程 可 以 极 大<br />

的 提 高 系 统 性 能 。<br />

什 么 是 线 程 ?<br />

每 个 正 在 系 统 上 运 行 的 程 序 都 是 一 个 进 程 。 每 个 进 程 包 含 一 到 多 个 线 程 。 进 程 也 可 能 是<br />

整 个 程 序 或 者 是 部 分 程 序 的 动 态 执 行 。 线 程 是 一 组 指 令 的 集 合 , 或 者 是 程 序 的 特 殊 段 , 它 可<br />

以 在 程 序 里 独 立 执 行 。 也 可 以 把 它 理 解 为 代 码 运 行 的 上 下 文 。 所 以 线 程 基 本 上 是 轻 量 级 的 进<br />

程 , 它 负 责 在 单 个 程 序 里 执 行 多 任 务 。 通 常 由 操 作 系 统 负 责 多 个 线 程 的 调 度 和 执 行 。<br />

什 么 是 多 线 程 ?<br />

多 线 程 是 为 了 使 得 多 个 线 程 并 行 的 工 作 以 完 成 多 项 任 务 , 以 提 高 系 统 的 效 率 。 线 程 是 在<br />

同 一 时 间 需 要 完 成 多 项 任 务 的 时 候 被 实 现 的 。<br />

使 用 线 程 的 好 处 有 以 下 几 点 :<br />

· 使 用 线 程 可 以 把 占 据 长 时 间 的 程 序 中 的 任 务 放 到 后 台 去 处 理<br />

· 用 户 界 面 可 以 更 加 吸 引 人 , 这 样 比 如 用 户 点 击 了 一 个 按 钮 去 触 发 某 些 事 件 的 处 理 , 可<br />

以 弹 出 一 个 进 度 条 来 显 示 处 理 的 进 度<br />

· 程 序 的 运 行 速 度 可 能 加 快


· 在 一 些 等 待 的 任 务 实 现 上 如 用 户 输 入 、 文 件 读 写 和 网 络 收 发 数 据 等 , 线 程 就 比 较 游 泳<br />

了 。 在 这 种 情 况 下 我 们 可 以 释 放 一 些 珍 贵 的 资 源 如 内 存 占 用 等 等 。<br />

还 有 其 他 很 多 使 用 多 线 程 的 好 处 , 这 里 就 不 一 一 说 明 了 。<br />

一 些 线 程 模 型 的 背 景<br />

我 们 可 以 重 点 讨 论 一 下 在 Win32 环 境 中 常 用 的 一 些 模 型 。<br />

· 单 线 程 模 型<br />

在 这 种 线 程 模 型 中 , 一 个 进 程 中 只 能 有 一 个 线 程 , 剩 下 的 进 程 必 须 等 待 当 前 的 线 程 执 行<br />

完 。 这 种 模 型 的 缺 点 在 于 系 统 完 成 一 个 很 小 的 任 务 都 必 须 占 用 很 长 的 时 间 。<br />

· 块 线 程 模 型 ( 单 线 程 多 块 模 型 STA)<br />

这 种 模 型 里 , 一 个 程 序 里 可 能 会 包 含 多 个 执 行 的 线 程 。 在 这 里 , 每 个 线 程 被 分 为 进 程 里<br />

一 个 单 独 的 块 。 每 个 进 程 可 以 含 有 多 个 块 , 可 以 共 享 多 个 块 中 的 数 据 。 程 序 规 定 了 每 个 块 中<br />

线 程 的 执 行 时 间 。 所 有 的 请 求 通 过 Windows 消 息 队 列 进 行 串 行 化 , 这 样 保 证 了 每 个 时 刻 只 能<br />

访 问 一 个 块 , 因 而 只 有 一 个 单 独 的 进 程 可 以 在 某 一 个 时 刻 得 到 执 行 。 这 种 模 型 比 单 线 程 模 型<br />

的 好 处 在 于 , 可 以 响 应 同 一 时 刻 的 多 个 用 户 请 求 的 任 务 而 不 只 是 单 个 用 户 请 求 。 但 它 的 性 能<br />

还 不 是 很 好 , 因 为 它 使 用 了 串 行 化 的 线 程 模 型 , 任 务 是 一 个 接 一 个 得 到 执 行 的 。<br />

· 多 线 程 块 模 型 ( 自 由 线 程 块 模 型 )<br />

多 线 程 块 模 型 (139HMTA) 在 每 个 进 程 里 只 有 一 个 块 而 不 是 多 个 块 。 这 单 个 块 控 制 着 多 个<br />

线 程 而 不 是 单 个 线 程 。 这 里 不 需 要 消 息 队 列 , 因 为 所 有 的 线 程 都 是 相 同 的 块 的 一 个 部 分 , 并<br />

且 可 以 共 享 。 这 样 的 程 序 比 单 线 程 模 型 和 STA 的 执 行 速 度 都 要 块 , 因 为 降 低 了 系 统 的 负 载 ,<br />

因 而 可 以 优 化 来 减 少 系 统 idle 的 时 间 。 这 些 应 用 程 序 一 般 比 较 复 杂 , 因 为 程 序 员 必 须 提 供 线


程 同 步 以 保 证 线 程 不 会 并 发 的 请 求 相 同 的 资 源 , 因 而 导 致 竞 争 情 况 的 发 生 。 这 里 有 必 要 提 供<br />

一 个 锁 机 制 。 但 是 这 样 也 许 会 导 致 系 统 死 锁 的 发 生 。<br />

多 线 程 在 .NET 里 如 何 工 作 ?<br />

在 本 质 上 和 结 构 来 说 ,.NET 是 一 个 多 线 程 的 环 境 。 有 两 种 主 要 的 多 线 程 方 法 是 .NET 所<br />

提 倡 的 : 使 用 ThreadStart 来 开 始 你 自 己 的 进 程 , 直 接 的 ( 使 用 ThreadPool.QueueUserWorkItem)<br />

或 者 间 接 的 ( 比 如 Stream.BeginRead, 或 者 调 用 BeginInvoke) 使 用 ThreadPool 类 。 一 般 来 说 ,<br />

你 可 以 " 手 动 " 为 长 时 间 运 行 的 任 务 创 建 一 个 新 的 线 程 , 另 外 对 于 短 时 间 运 行 的 任 务 尤 其 是 经<br />

常 需 要 开 始 的 那 些 , 进 程 池 是 一 个 非 常 好 的 选 择 。 进 程 池 可 以 同 时 运 行 多 个 任 务 , 还 可 以 使<br />

用 框 架 类 。 对 于 资 源 紧 缺 需 要 进 行 同 步 的 情 况 来 说 , 它 可 以 限 制 某 一 时 刻 只 允 许 一 个 线 程 访<br />

问 资 源 。 这 种 情 况 可 以 视 为 给 线 程 实 现 了 锁 机 制 。 线 程 的 基 类 是 System.Threading。 所 有 线<br />

程 通 过 CLI 来 进 行 管 理 。<br />

· 创 建 线 程 :<br />

创 建 一 个 新 的 Thread 对 象 的 实 例 。Thread 的 构 造 函 数 接 受 一 个 参 数 :<br />

Thread DummyThread = new Thread( new ThreadStart(dummyFunction) );<br />

· 执 行 线 程 :<br />

使 用 Threading 命 名 空 间 里 的 start 方 法 来 运 行 线 程 :<br />

DummyThread.Start ();<br />

· 组 合 线 程 :<br />

经 常 会 出 现 需 要 组 合 多 个 线 程 的 情 况 , 就 是 当 某 个 线 程 需 要 其 他 线 程 的 结 束 来 完 成 自 己<br />

的 任 务 。 假 设 DummyThread 必 须 等 待 DummyPriorityThread 来 完 成 自 己 的 任 务 , 我 们 只 需<br />

要 这 样 做 :


DummyPriorityThread.Join() ;<br />

· 暂 停 线 程 :<br />

使 得 线 程 暂 停 给 定 的 秒<br />

DummyPriorityThread.140HSleep();<br />

· 中 止 线 程 :<br />

如 果 需 要 中 止 线 程 可 以 使 用 如 下 的 代 码 :<br />

DummyPriorityThread.Abort();<br />

· 同 步<br />

经 常 我 们 会 遇 到 需 要 在 线 程 间 进 行 同 步 的 情 况 , 下 面 的 代 码 给 出 了 一 些 方 法 :<br />

141Husing System;using System.Threading;namespace SynchronizationThreadsExample{<br />

class SynchronizationThreadsExample{<br />

private int counter = 0;<br />

142Hstatic void Main( ) {<br />

SynchronizationThreadsExample STE = new SynchronizationThreadsExample();<br />

STE.ThreadFunction( );<br />

}<br />

public void ThreadFunction ( ) {<br />

Thread DummyThread = new Thread( new ThreadStart(SomeFunction) ;<br />

DummyThread.IsBackground=true;<br />

DummyThread.Name = "First Thread";<br />

DummyThread.Start( );<br />

Console.WriteLine("Started thread {0}", DummyThread.Name);<br />

Thread DummyPriorityThread = new Thread( new ThreadStart(SomeFunction) );<br />

DummyPriorityThread.IsBackground=true;<br />

DummyPriorityThread.Name = "Second Thread";<br />

DummyPriorityThread.Start( );<br />

Console.WriteLine("Started thread {0}", DummyPriorityThread.Name);<br />

DummyThread.Join( );


DummyPriorityThread.Join( );<br />

}<br />

public void SomeFunction( ) {<br />

try {<br />

while (counter < 10) {<br />

int tempCounter = counter;<br />

tempCounter ++;<br />

Thread.Sleep(1);<br />

counter = tempCounter;<br />

Console.WriteLine( "Thread {0}. SomeFunction: {1}",Thread.CurrentThread.Name,<br />

counter);<br />

}<br />

}<br />

catch (ThreadInterruptedException Ex) {<br />

Console.WriteLine( "Exception in thread {0} ", Thread.CurrentThread.Name);<br />

}<br />

finally {<br />

Console.WriteLine( "Thread {0} Exiting. ",Thread.CurrentThread.Name);<br />

}<br />

}<br />

}<br />

}<br />

· 使 用 Interlock<br />

C# 提 供 了 一 个 特 殊 的 类 叫 做 interlocked, 就 是 提 供 了 锁 机 制 的 实 现 , 我 们 可 以 加 入 如 下<br />

的 代 码 实 现 锁 机 制 :<br />

Interlocked.SomeFunction (ref counter);<br />

· 使 用 锁<br />

这 是 为 了 锁 定 代 码 关 键 区 域 以 进 行 同 步 , 锁 定 代 码 如 下 :<br />

lock (this){ Some statements ;}<br />

· 使 用 143HMonitor


当 有 需 要 进 行 线 程 管 理 的 时 候 我 们 可 以 使 用 :<br />

Monitor.Enter(this);<br />

其 他 也 有 一 些 方 法 进 行 管 理 , 这 里 就 不 一 一 提 及 了 。<br />

线 程 的 缺 点<br />

线 程 自 然 也 有 缺 点 , 以 下 列 出 了 一 些 :<br />

· 如 果 有 大 量 的 线 程 , 会 影 响 性 能 , 因 为 操 作 系 统 需 要 在 他 们 之 间 切 换 ;<br />

· 更 多 的 线 程 需 要 更 多 的 内 存 空 间<br />

· 线 程 会 给 程 序 带 来 更 多 的 bug, 因 此 要 小 心 使 用<br />

· 线 程 的 中 止 需 要 考 虑 其 对 程 序 运 行 的 影 响<br />

· 通 常 块 模 型 数 据 是 在 多 个 线 程 间 共 享 的 , 需 要 一 个 合 适 的 锁 系 统 替 换 掉 数 据 共 享


浅 析 .Net 下 的 多 线 程 编 程<br />

admin 发 表 于 :04-04-24<br />

多 线 程 是 许 多 操 作 系 统 所 具 有 的 特 性 , 它 能 大 大 提 高 程 序 的 运 行 效 率 , 所 以 多 线 程 编 程 技 术<br />

为 编 程 者 广 泛 关 注 。 目 前 微 软 的 .Net 战 略 正 进 一 步 推 进 , 各 种 相 关 的 技 术 正 为 广 大 编 程 者 所<br />

接 受 , 同 样 在 .Net 中 多 线 程 编 程 技 术 具 有 相 当 重 要 的 地 位 。 本 文 我 就 向 大 家 介 绍 在 .Net 下 进<br />

行 多 线 程 编 程 的 基 本 方 法 和 步 骤 。<br />

开 始 新 线 程<br />

在 .Net 下 创 建 一 个 新 线 程 是 非 常 容 易 的 , 你 可 以 通 过 以 下 的 语 句 来 开 始 一 个 新 的 线 程 :<br />

Thread thread = new Thread (new ThreadStart (ThreadFunc));<br />

thread.Start ();<br />

第 一 条 语 句 创 建 一 个 新 的 Thread 对 象 , 并 指 明 了 一 个 该 线 程 的 方 法 。 当 新 的 线 程 开 始 时 ,<br />

该 方 法 也 就 被 调 用 执 行 了 。 该 线 程 对 象 通 过 一 个 System..Threading.ThreadStart 类 的 一 个 实 例<br />

以 类 型 安 全 的 方 法 来 调 用 它 要 调 用 的 线 程 方 法 。<br />

第 二 条 语 句 正 式 开 始 该 新 线 程 , 一 旦 方 法 Start() 被 调 用 , 该 线 程 就 保 持 在 一 个 "alive" 的 状<br />

态 下 了 , 你 可 以 通 过 读 取 它 的 IsAlive 属 性 来 判 断 它 是 否 处 于 "alive" 状 态 。 下 面 的 语 句 显 示<br />

了 如 果 一 个 线 程 处 于 "alive" 状 态 下 就 将 该 线 程 挂 起 的 方 法 :<br />

if (thread.IsAlive) {<br />

thread.Suspend ();<br />

}<br />

不 过 请 注 意 , 线 程 对 象 的 Start() 方 法 只 是 启 动 了 该 线 程 , 而 并 不 保 证 其 线 程 方 法 ThreadFunc


() 能 立 即 得 到 执 行 。 它 只 是 保 证 该 线 程 对 象 能 被 分 配 到 CPU 时 间 , 而 实 际 的 执 行 还 要 由<br />

操 作 系 统 根 据 处 理 器 时 间 来 决 定 。<br />

一 个 线 程 的 方 法 不 包 含 任 何 参 数 , 同 时 也 不 返 回 任 何 值 。 它 的 命 名 规 则 和 一 般 函 数 的 命 名 规<br />

则 相 同 。 它 既 可 以 是 静 态 的 (static) 也 可 以 是 非 静 态 的 (nonstatic)。 当 它 执 行 完 毕 后 , 相<br />

应 的 线 程 也 就 结 束 了 , 其 线 程 对 象 的 IsAlive 属 性 也 就 被 置 为 false 了 。 下 面 是 一 个 线 程 方 法<br />

的 实 例 :<br />

public static void ThreadFunc()<br />

{<br />

for (int i = 0; i


很 快 就 运 行 结 束 了 , 但 程 序 要 到 所 有 已 启 动 的 线 程 都 运 行 完 毕 才 会 结 束 。 示 例 代 码 如 下 :<br />

using System;<br />

using System.Threading;<br />

class MyApp<br />

{<br />

public static void Main ()<br />

{<br />

for (int i=0; i


接 下 来 我 们 对 上 面 的 代 码 进 行 略 微 修 改 , 将 每 个 线 程 的 IsBackground 属 性 都 设 置 为 true, 则<br />

每 个 线 程 都 是 后 台 线 程 了 。 那 么 只 要 程 序 的 主 线 程 结 束 了 , 整 个 程 序 也 就 结 束 了 。 示 例 代 码<br />

如 下 :<br />

using System;<br />

using System.Threading;<br />

class MyApp<br />

{<br />

public static void Main ()<br />

{<br />

for (int i=0; i


既 然 前 台 线 程 和 后 台 线 程 有 这 种 差 别 , 那 么 我 们 怎 么 知 道 该 如 何 设 置 一 个 线 程 的<br />

IsBackground 属 性 呢 ? 下 面 是 一 些 基 本 的 原 则 : 对 于 一 些 在 后 台 运 行 的 线 程 , 当 程 序 结 束 时<br />

这 些 线 程 没 有 必 要 继 续 运 行 了 , 那 么 这 些 线 程 就 应 该 设 置 为 后 台 线 程 。 比 如 一 个 程 序 启 动 了<br />

一 个 进 行 大 量 运 算 的 线 程 , 可 是 只 要 程 序 一 旦 结 束 , 那 个 线 程 就 失 去 了 继 续 存 在 的 意 义 , 那<br />

么 那 个 线 程 就 该 是 作 为 后 台 线 程 的 。 而 对 于 一 些 服 务 于 用 户 界 面 的 线 程 往 往 是 要 设 置 为 前 台<br />

线 程 的 , 因 为 即 使 程 序 的 主 线 程 结 束 了 , 其 他 的 用 户 界 面 的 线 程 很 可 能 要 继 续 存 在 来 显 示 相<br />

关 的 信 息 , 所 以 不 能 立 即 终 止 它 们 。 这 里 我 只 是 给 出 了 一 些 原 则 , 具 体 到 实 际 的 运 用 往 往 需<br />

要 编 程 者 的 进 一 步 仔 细 斟 酌 。<br />

线 程 优 先 级<br />

一 旦 一 个 线 程 开 始 运 行 , 线 程 调 度 程 序 就 可 以 控 制 其 所 获 得 的 CPU 时 间 。 如 果 一 个 托 管 的<br />

应 用 程 序 运 行 在 Windows 机 器 上 , 则 线 程 调 度 程 序 是 由 Windows 所 提 供 的 。 在 其 他 的 平 台<br />

上 , 线 程 调 度 程 序 可 能 是 操 作 系 统 的 一 部 分 , 也 自 然 可 能 是 .Net 框 架 的 一 部 分 。 不 过 我 们 这<br />

里 不 必 考 虑 线 程 的 调 度 程 序 是 如 何 产 生 的 , 我 们 只 要 知 道 通 过 设 置 线 程 的 优 先 级 我 们 就 可 以<br />

使 该 线 程 获 得 不 同 的 CPU 时 间 。<br />

线 程 的 优 先 级 是 由 Thread.Priority 属 性 控 制 的 , 其 值 包 含 :ThreadPriority.Highest 、<br />

ThreadPriority.AboveNormal 、 ThreadPriority.Normal 、 ThreadPriority.BelowNormal 和<br />

ThreadPriority.Lowest。 从 它 们 的 名 称 上 我 们 自 然 可 以 知 道 它 们 的 优 先 程 度 , 所 以 这 里 就 不 多<br />

作 介 绍 了 。<br />

线 程 的 默 认 优 先 级 为 ThreadPriority.Normal。 理 论 上 , 具 有 相 同 优 先 级 的 线 程 会 获 得 相 同 的<br />

CPU 时 间 , 不 过 在 实 际 执 行 时 , 消 息 队 列 中 的 线 程 阻 塞 或 是 操 作 系 统 的 优 先 级 的 提 高 等 原<br />

因 会 导 致 具 有 相 同 优 先 级 的 线 程 会 获 得 不 同 的 CPU 时 间 。 不 过 从 总 体 上 来 考 虑 仍 可 以 忽 略<br />

这 种 差 异 。 你 可 以 通 过 以 下 的 方 法 来 改 变 一 个 线 程 的 优 先 级 。<br />

thread.Priority = ThreadPriority.AboveNormal;<br />

或 是 :


thread.Priority = ThreadPriority.BelowNormal;<br />

通 过 上 面 的 第 一 句 语 句 你 可 以 提 高 一 个 线 程 的 优 先 级 , 那 么 该 线 程 就 会 相 应 的 获 得 更 多 的<br />

CPU 时 间 ; 通 过 第 二 句 语 句 你 便 降 低 了 那 个 线 程 的 优 先 级 , 于 是 它 就 会 被 分 配 到 比 原 来 少<br />

的 CPU 时 间 了 。 你 可 以 在 一 个 线 程 开 始 运 行 前 或 是 在 它 的 运 行 过 程 中 的 任 何 时 候 改 变 它 的<br />

优 先 级 。 理 论 上 你 还 可 以 任 意 的 设 置 每 个 线 程 的 优 先 级 , 不 过 一 个 优 先 级 过 高 的 线 程 往 往 会<br />

影 响 到 其 他 线 程 的 运 行 , 甚 至 影 响 到 其 他 程 序 的 运 行 , 所 以 最 好 不 要 随 意 的 设 置 线 程 的 优 先<br />

级 。<br />

挂 起 线 程 和 重 新 开 始 线 程<br />

Thread 类 分 别 提 供 了 两 个 方 法 来 挂 起 线 程 和 重 新 开 始 线 程 , 也 就 是 Thread.Suspend 能 暂 停 一<br />

个 正 在 运 行 的 线 程 , 而 Thread.Resume 又 能 让 那 个 线 程 继 续 运 行 。 不 像 Windows 内 核 ,.Net<br />

框 架 是 不 记 录 线 程 的 挂 起 次 数 的 , 所 以 不 管 你 挂 起 线 程 过 几 次 , 只 要 一 次 调 用 Thread.Resume<br />

就 可 以 让 挂 起 的 线 程 重 新 开 始 运 行 。<br />

Thread 类 还 提 供 了 一 个 静 态 的 Thread.Sleep 方 法 , 它 能 使 一 个 线 程 自 动 的 挂 起 一 定 的 时 间 ,<br />

然 后 自 动 的 重 新 开 始 。 一 个 线 程 能 在 它 自 身 内 部 调 用 Thread.Sleep 方 法 , 也 能 在 自 身 内 部 调<br />

用 Thread.Suspend 方 法 , 可 是 一 定 要 别 的 线 程 来 调 用 它 的 Thread.Resume 方 法 才 可 以 重 新 开<br />

始 。 这 一 点 是 不 是 很 容 易 想 通 的 啊 ? 下 面 的 例 子 显 示 了 如 何 运 用 Thread.Sleep 方 法 :<br />

while (ContinueDrawing) {<br />

DrawNextSlide ();<br />

Thread.Sleep (5000);<br />

}<br />

终 止 线 程<br />

在 托 管 的 代 码 中 , 你 可 以 通 过 以 下 的 语 句 在 一 个 线 程 中 将 另 一 个 线 程 终 止 掉 :<br />

thread.Abort ();<br />

下 面 我 们 来 解 释 一 下 Abort() 方 法 是 如 何 工 作 的 。 因 为 公 用 语 言 运 行 时 管 理 了 所 有 的 托 管


的 线 程 , 同 样 它 能 在 每 个 线 程 内 抛 出 异 常 。Abort() 方 法 能 在 目 标 线 程 中 抛 出 一 个<br />

ThreadAbortException 异 常 从 而 导 致 目 标 线 程 的 终 止 。 不 过 Abort() 方 法 被 调 用 后 , 目 标 线<br />

程 可 能 并 不 是 马 上 就 终 止 了 。 因 为 只 要 目 标 线 程 正 在 调 用 非 托 管 的 代 码 而 且 还 没 有 返 回 的<br />

话 , 该 线 程 就 不 会 立 即 终 止 。 而 如 果 目 标 线 程 在 调 用 非 托 管 的 代 码 而 且 陷 入 了 一 个 死 循 环 的<br />

话 , 该 目 标 线 程 就 根 本 不 会 终 止 。 不 过 这 种 情 况 只 是 一 些 特 例 , 更 多 的 情 况 是 目 标 线 程 在 调<br />

用 托 管 的 代 码 , 一 旦 Abort() 被 调 用 那 么 该 线 程 就 立 即 终 止 了 。<br />

在 实 际 应 用 中 , 一 个 线 程 终 止 了 另 一 个 线 程 , 不 过 往 往 要 等 那 个 线 程 完 全 终 止 了 它 才 可 以 继<br />

续 运 行 , 这 样 的 话 我 们 就 应 该 用 到 它 的 Join() 方 法 。 示 例 代 码 如 下 :<br />

thread.Abort (); // 要 求 终 止 另 一 个 线 程<br />

thread.Join (); // 只 到 另 一 个 线 程 完 全 终 止 了 , 它 才 继 续 运 行<br />

但 是 如 果 另 一 个 线 程 一 直 不 能 终 止 的 话 ( 原 因 如 前 所 述 ), 我 们 就 需 要 给 Join() 方 法 设 置<br />

一 个 时 间 限 制 , 方 法 如 下 :<br />

thread.Join (5000); // 暂 停 5 秒<br />

这 样 , 在 5 秒 后 , 不 管 那 个 线 程 有 没 有 完 全 终 止 , 本 线 程 就 强 行 运 行 了 。 该 方 法 还 返 回 一 个<br />

布 尔 型 的 值 , 如 果 是 true 则 表 明 那 个 线 程 已 经 完 全 终 止 了 , 而 如 果 是 false 的 话 , 则 表 明 已<br />

经 超 过 了 时 间 限 制 了 。<br />

时 钟 线 程<br />

.Net 框 架 中 的 Timer 类 可 以 让 你 使 用 时 钟 线 程 , 它 是 包 含 在 System.Threading 名 字 空 间 中 的 ,<br />

它 的 作 用 就 是 在 一 定 的 时 间 间 隔 后 调 用 一 个 线 程 的 方 法 。 下 面 我 给 大 家 展 示 一 个 具 体 的 实<br />

例 , 该 实 例 以 1 秒 为 时 间 间 隔 , 在 控 制 台 中 输 出 不 同 的 字 符 串 , 代 码 如 下 :<br />

using System;<br />

using System.Threading;<br />

class MyApp<br />

{<br />

private static bool TickNext = true;<br />

public static void Main ()<br />

{<br />

Console.WriteLine ("Press Enter to terminate...");


TimerCallback callback = new TimerCallback (TickTock);<br />

Timer timer = new Timer (callback, null, 1000, 1000);<br />

Console.ReadLine ();<br />

}<br />

private static void TickTock (object state)<br />

{<br />

Console.WriteLine (TickNext ? "Tick" : "Tock");<br />

TickNext = ! TickNext;<br />

}<br />

}<br />

从 上 面 的 代 码 中 , 我 们 知 道 第 一 个 函 数 回 调 是 在 1000 毫 秒 后 才 发 生 的 , 以 后 的 函 数 回 调 也<br />

是 在 每 隔 1000 毫 秒 之 后 发 生 的 , 这 是 由 Timer 对 象 的 构 造 函 数 中 的 第 三 个 参 数 所 决 定 的 。<br />

程 序 会 在 1000 毫 秒 的 时 间 间 隔 后 不 断 的 产 生 新 线 程 , 只 到 用 户 输 入 回 车 才 结 束 运 行 。 不 过<br />

值 得 注 意 的 是 , 虽 然 我 们 设 置 了 时 间 间 隔 为 1000 毫 秒 , 但 是 实 际 运 行 的 时 候 往 往 并 不 能 非<br />

常 精 确 。 因 为 Windows 操 作 系 统 并 不 是 一 个 实 时 系 统 , 而 公 用 语 言 运 行 时 也 不 是 实 时 的 ,<br />

所 以 由 于 线 程 调 度 的 千 变 万 化 , 实 际 的 运 行 效 果 往 往 是 不 能 精 确 到 毫 秒 级 的 , 但 是 对 于 一 般<br />

的 应 用 来 说 那 已 经 是 足 够 的 了 , 所 以 你 也 不 必 十 分 苛 求 。<br />

小 结<br />

本 文 介 绍 了 在 .Net 下 进 行 多 线 程 编 程 所 需 要 掌 握 的 一 些 基 本 知 识 。 从 文 章 中 我 们 可 以 知 道<br />

在 .Net 下 进 行 多 线 程 编 程 相 对 以 前 是 有 了 大 大 的 简 化 , 但 是 其 功 能 并 没 有 被 削 弱 。 使 用 以 上<br />

的 一 些 基 本 知 识 , 读 者 就 可 以 试 着 编 写 .Net 下 的 多 线 程 程 序 了 。 不 过 要 编 写 出 功 能 更 加 强 大<br />

而 且 Bug 少 的 多 线 程 应 用 程 序 , 读 者 需 要 掌 握 诸 如 线 程 同 步 、 线 程 池 等 高 级 的 多 线 程 编 程<br />

技 术 。 读 者 不 妨 参 考 一 些 操 作 系 统 方 面 或 是 多 线 程 编 程 方 面 的 技 术 丛 书 。


144H 通 过 多 线 程 为 基 于 .NET 的 应 用 程 序 实 现 响 应 迅 速 的 用 户<br />

通 过 多 线 程 为 基 于 .NET 的 应 用 程 序 实 现 响 应 迅 速 的 用 户<br />

Ian Griffiths<br />

摘 要<br />

如 果 应 用 程 序 在 控 制 用 户 界 面 的 线 程 上 执 行 非 UI 处 理 , 则 会 使 应 用 程 序 的 运 行 显 得 缓 慢 而 迟 钝 , 让<br />

用 户 难 以 忍 受 。 但 是 长 期 以 来 , 编 写 适 用 于 Windows 的 多 线 程 应 用 程 序 只 限 于 C++ 开 发 人 员 。 现<br />

在 有 了 .NET Framework, 您 就 可 以 充 分 利 用 C# 中 的 多 线 程 来 控 制 程 序 中 的 指 令 流 , 并 使 UI 线 程<br />

独 立 出 来 以 便 用 户 界 面 能 够 迅 速 响 应 。 本 文 将 向 您 介 绍 如 何 实 现 这 一 目 标 。 此 外 , 本 文 还 将 讨 论 多 线<br />

程 的 缺 陷 并 提 供 一 个 框 架 来 保 护 并 发 线 程 执 行 的 安 全 。<br />

用 户 不 喜 欢 反 应 慢 的 程 序 。 程 序 反 应 越 慢 , 就 越 没 有 用 户 会 喜 欢 它 。 在 执 行 耗 时 较 长 的 操 作 时 , 使 用<br />

多 线 程 是 明 智 之 举 , 它 可 以 提 高 程 序 UI 的 响 应 速 度 , 使 得 一 切 运 行 显 得 更 为 快 速 。 在 Windows 中<br />

进 行 多 线 程 编 程 曾 经 是 C++ 开 发 人 员 的 专 属 特 权 , 但 是 现 在 , 可 以 使 用 所 有 兼 容 Microsoft .NET 的<br />

语 言 来 编 写 , 其 中 包 括 Visual Basic.NET。 不 过 ,Windows 窗 体 对 线 程 的 使 用 强 加 了 一 些 重 要 限 制 。<br />

本 文 将 对 这 些 限 制 进 行 阐 释 , 并 说 明 如 何 利 用 它 们 来 提 供 快 速 、 高 质 量 的 UI 体 验 , 即 使 是 程 序 要 执<br />

行 的 任 务 本 身 速 度 就 较 慢 。<br />

为 什 么 选 择 多 线 程 ?


多 线 程 程 序 要 比 单 线 程 程 序 更 难 于 编 写 , 并 且 不 加 选 择 地 使 用 线 程 也 是 导 致 难 以 找 到 细 小 错 误 的 重 要<br />

原 因 。 这 就 自 然 会 引 出 两 个 问 题 : 为 什 么 不 坚 持 编 写 单 线 程 代 码 ? 如 果 必 须 使 用 多 线 程 , 如 何 才 能 避<br />

免 缺 陷 呢 ? 本 文 的 大 部 分 篇 幅 都 是 在 回 答 第 二 个 问 题 , 但 首 先 我 要 来 解 释 一 下 为 什 么 确 实 需 要 多 线<br />

程 。<br />

多 线 程 处 理 可 以 使 您 能 够 通 过 确 保 程 序 “ 永 不 睡 眠 ” 从 而 保 持 UI 的 快 速 响 应 。 大 部 分 程 序 都 有 不 响 应<br />

用 户 的 时 候 : 它 们 正 忙 于 为 您 执 行 某 些 操 作 以 便 响 应 进 一 步 的 请 求 。 也 许 最 广 为 人 知 的 例 子 就 是 出 现<br />

在 “ 打 开 文 件 ” 对 话 框 顶 部 的 组 合 框 。 如 果 在 展 开 该 组 合 框 时 ,CD-ROM 驱 动 器 里 恰 好 有 一 张 光 盘 , 则<br />

计 算 机 通 常 会 在 显 示 列 表 之 前 先 读 取 光 盘 。 这 可 能 需 要 几 秒 钟 的 时 间 , 在 此 期 间 , 程 序 既 不 响 应 任 何<br />

输 入 , 也 不 允 许 取 消 该 操 作 , 尤 其 是 在 确 实 并 不 打 算 使 用 光 驱 的 时 候 , 这 种 情 况 会 让 人 无 法 忍 受 。<br />

执 行 这 种 操 作 期 间 UI 冻 结 的 原 因 在 于 ,UI 是 个 单 线 程 程 序 , 单 线 程 不 可 能 在 等 待 CD-ROM 驱 动 器<br />

读 取 操 作 的 同 时 处 理 用 户 输 入 , 如 图 1 所 示 。“ 打 开 文 件 ” 对 话 框 会 调 用 某 些 阻 塞 (blocking) API 来<br />

确 定 CD-ROM 的 标 题 。 阻 塞 API 在 未 完 成 自 己 的 工 作 之 前 不 会 返 回 , 因 此 这 期 间 它 会 阻 止 线 程 做<br />

其 他 事 情 。<br />

图 1 单 线 程<br />

在 多 线 程 下 , 像 这 样 耗 时 较 长 的 任 务 就 可 以 在 其 自 己 的 线 程 中 运 行 , 这 些 线 程 通 常 称 为 辅 助 线 程 。 因<br />

为 只 有 辅 助 线 程 受 到 阻 止 , 所 以 阻 塞 操 作 不 再 导 致 用 户 界 面 冻 结 , 如 图 2 所 示 。 应 用 程 序 的 主 线 程<br />

可 以 继 续 处 理 用 户 的 鼠 标 和 键 盘 输 入 的 同 时 , 受 阻 的 另 一 个 线 程 将 等 待 CD-ROM 读 取 , 或 执 行 辅 助<br />

线 程 可 能 做 的 任 何 操 作 。<br />

图 2 多 线 程


其 基 本 原 则 是 , 负 责 响 应 用 户 输 入 和 保 持 用 户 界 面 为 最 新 的 线 程 ( 通 常 称 为 UI 线 程 ) 不 应 该 用 于<br />

执 行 任 何 耗 时 较 长 的 操 作 。 惯 常 做 法 是 , 任 何 耗 时 超 过 30ms 的 操 作 都 要 考 虑 从 UI 线 程 中 移 除 。<br />

这 似 乎 有 些 夸 张 , 因 为 30ms 对 于 大 多 数 人 而 言 只 不 过 是 他 们 可 以 感 觉 到 的 最 短 的 瞬 间 停 顿 , 实 际<br />

上 该 停 顿 略 短 于 电 影 屏 幕 中 显 示 的 连 续 帧 之 间 的 间 隔 。<br />

如 果 鼠 标 单 击 和 相 应 的 UI 提 示 ( 例 如 , 重 新 绘 制 按 钮 ) 之 间 的 延 迟 超 过 30ms, 那 么 操 作 与 显 示 之<br />

间 就 会 稍 显 不 连 贯 , 并 因 此 产 生 如 同 影 片 断 帧 那 样 令 人 心 烦 的 感 觉 。 为 了 达 到 完 全 高 质 量 的 响 应 效 果 ,<br />

上 限 必 须 是 30ms。 另 一 方 面 , 如 果 您 确 实 不 介 意 感 觉 稍 显 不 连 贯 , 但 也 不 想 因 为 停 顿 过 长 而 激 怒 用<br />

户 , 则 可 按 照 通 常 用 户 所 能 容 忍 的 限 度 将 该 间 隔 设 为 100ms。<br />

这 意 味 着 如 果 想 让 用 户 界 面 保 持 响 应 迅 速 , 则 任 何 阻 塞 操 作 都 应 该 在 辅 助 线 程 中 执 行 — 不 管 是 机 械<br />

等 待 某 事 发 生 ( 例 如 , 等 待 CD-ROM 启 动 或 者 硬 盘 定 位 数 据 ), 还 是 等 待 来 自 网 络 的 响 应 。<br />

异 步 委 托 调 用<br />

在 辅 助 线 程 中 运 行 代 码 的 最 简 单 方 式 是 使 用 异 步 委 托 调 用 ( 所 有 委 托 都 提 供 该 功 能 )。 委 托 通 常 是 以<br />

同 步 方 式 进 行 调 用 , 即 , 在 调 用 委 托 时 , 只 有 包 装 方 法 返 回 后 该 调 用 才 会 返 回 。 要 以 异 步 方 式 调 用 委<br />

托 , 请 调 用 BeginInvoke 方 法 , 这 样 会 对 该 方 法 排 队 以 在 系 统 线 程 池 的 线 程 中 运 行 。 调 用 线 程 会 立<br />

即 返 回 , 而 不 用 等 待 该 方 法 完 成 。 这 比 较 适 合 于 UI 程 序 , 因 为 可 以 用 它 来 启 动 耗 时 较 长 的 作 业 , 而<br />

不 会 使 用 户 界 面 反 应 变 慢 。<br />

例 如 , 在 以 下 代 码 中 ,System.Windows.Forms.MethodInvoker 类 型 是 一 个 系 统 定 义 的 委 托 , 用 于 调<br />

用 不 带 参 数 的 方 法 。<br />

private void StartSomeWorkFromUIThread () {<br />

// The work we want to do is too slow for the UI<br />

// thread, so let's farm it out to a worker thread.<br />

MethodInvoker mi = new MethodInvoker(RunsOnWorkerThread);


mi.BeginInvoke(null, null); // This will not block.<br />

}<br />

// The slow work is done here, on a thread<br />

// from the system thread pool.<br />

private void RunsOnWorkerThread() {<br />

DoSomethingSlow();<br />

}<br />

如 果 想 要 传 递 参 数 , 可 以 选 择 合 适 的 系 统 定 义 的 委 托 类 型 , 或 者 自 己 来 定 义 委 托 。MethodInvoker 委<br />

托 并 没 有 什 么 神 奇 之 处 。 和 其 他 委 托 一 样 , 调 用 BeginInvoke 会 使 该 方 法 在 系 统 线 程 池 的 线 程 中 运<br />

行 , 而 不 会 阻 塞 UI 线 程 以 便 其 可 执 行 其 他 操 作 。 对 于 以 上 情 况 , 该 方 法 不 返 回 数 据 , 所 以 启 动 它 后<br />

就 不 用 再 去 管 它 。 如 果 您 需 要 该 方 法 返 回 的 结 果 , 则 BeginInvoke 的 返 回 值 很 重 要 , 并 且 您 可 能 不<br />

传 递 空 参 数 。 然 而 , 对 于 大 多 数 UI 应 用 程 序 而 言 , 这 种 “ 启 动 后 就 不 管 ” 的 风 格 是 最 有 效 的 , 稍 后 会<br />

对 原 因 进 行 简 要 讨 论 。 您 应 该 注 意 到 ,BeginInvoke 将 返 回 一 个 IAsyncResult。 这 可 以 和 委 托 的<br />

EndInvoke 方 法 一 起 使 用 , 以 在 该 方 法 调 用 完 毕 后 检 索 调 用 结 果 。<br />

还 有 其 他 一 些 可 用 于 在 另 外 的 线 程 上 运 行 方 法 的 技 术 , 例 如 , 直 接 使 用 线 程 池 API 或 者 创 建 自 己 的<br />

线 程 。 然 而 , 对 于 大 多 数 用 户 界 面 应 用 程 序 而 言 , 有 异 步 委 托 调 用 就 足 够 了 。 采 用 这 种 技 术 不 仅 编 码<br />

容 易 , 而 且 还 可 以 避 免 创 建 并 非 必 需 的 线 程 , 因 为 可 以 利 用 线 程 池 中 的 共 享 线 程 来 提 高 应 用 程 序 的 整<br />

体 性 能 。<br />

线 程 和 控 件


Windows 窗 体 体 系 结 构 对 线 程 使 用 制 定 了 严 格 的 规 则 。 如 果 只 是 编 写 单 线 程 应 用 程 序 , 则 没 必 要 知<br />

道 这 些 规 则 , 这 是 因 为 单 线 程 的 代 码 不 可 能 违 反 这 些 规 则 。 然 而 , 一 旦 采 用 多 线 程 , 就 需 要 理 解<br />

Windows 窗 体 中 最 重 要 的 一 条 线 程 规 则 : 除 了 极 少 数 的 例 外 情 况 , 否 则 都 不 要 在 它 的 创 建 线 程 以 外<br />

的 线 程 中 使 用 控 件 的 任 何 成 员 。<br />

本 规 则 的 例 外 情 况 有 文 档 说 明 , 但 这 样 的 情 况 非 常 少 。 这 适 用 于 其 类 派 生 自<br />

System.Windows.Forms.Control 的 任 何 对 象 , 其 中 几 乎 包 括 UI 中 的 所 有 元 素 。 所 有 的 UI 元 素 ( 包<br />

括 表 单 本 身 ) 都 是 从 Control 类 派 生 的 对 象 。 此 外 , 这 条 规 则 的 结 果 是 一 个 被 包 含 的 控 件 ( 如 , 包 含<br />

在 一 个 表 单 中 的 按 钮 ) 必 须 与 包 含 它 控 件 位 处 于 同 一 个 线 程 中 。 也 就 是 说 , 一 个 窗 口 中 的 所 有 控 件 属<br />

于 同 一 个 UI 线 程 。 实 际 中 , 大 部 分 Windows 窗 体 应 用 程 序 最 终 都 只 有 一 个 线 程 , 所 有 UI 活 动 都<br />

发 生 在 这 个 线 程 上 。 这 个 线 程 通 常 称 为 UI 线 程 。 这 意 味 着 您 不 能 调 用 用 户 界 面 中 任 意 控 件 上 的 任 何<br />

方 法 , 除 非 在 该 方 法 的 文 档 说 明 中 指 出 可 以 调 用 。 该 规 则 的 例 外 情 况 ( 总 有 文 档 记 录 ) 非 常 少 而 且 它<br />

们 之 间 关 系 也 不 大 。 请 注 意 , 以 下 代 码 是 非 法 的 :<br />

// Created on UI thread<br />

private Label lblStatus;<br />

...<br />

// Doesn't run on UI thread<br />

private void RunsOnWorkerThread() {<br />

DoSomethingSlow();<br />

lblStatus.Text = "Finished!";<br />

// BAD!!<br />

}<br />

图 3 多 线 程<br />

如 果 您 在 .NET Framework 1.0 版 本 中 尝 试 运 行 这 段 代 码 , 也 许 会 侥 幸 运 行 成 功 , 或 者 初 看 起 来 是 如<br />

此 。 这 就 是 多 线 程 错 误 中 的 主 要 问 题 , 即 它 们 并 不 会 立 即 显 现 出 来 。 甚 至 当 出 现 了 一 些 错 误 时 , 在 第


一 次 演 示 程 序 之 前 一 切 看 起 来 也 都 很 正 常 。 但 不 要 搞 错 — 我 刚 才 显 示 的 这 段 代 码 明 显 违 反 了 规 则 ,<br />

并 且 可 以 预 见 , 任 何 抱 希 望 于 “ 试 运 行 时 良 好 , 应 该 就 没 有 问 题 ” 的 人 在 即 将 到 来 的 调 试 期 是 会 付 出 沉<br />

重 代 价 的 。<br />

要 注 意 , 在 明 确 创 建 线 程 之 前 会 发 生 这 样 的 问 题 。 使 用 委 托 的 异 步 调 用 实 用 程 序 ( 调 用 它 的<br />

BeginInvoke 方 法 ) 的 任 何 代 码 都 可 能 出 现 同 样 的 问 题 。 委 托 提 供 了 一 个 非 常 吸 引 人 的 解 决 方 案 来 处<br />

理 UI 应 用 程 序 中 缓 慢 、 阻 塞 的 操 作 , 因 为 这 些 委 托 能 使 您 轻 松 地 让 此 种 操 作 运 行 在 UI 线 程 外 而 无<br />

需 自 己 创 建 新 线 程 。 但 是 由 于 以 异 步 委 托 调 用 方 式 运 行 的 代 码 在 一 个 来 自 线 程 池 的 线 程 中 运 行 , 所 以<br />

它 不 能 访 问 任 何 UI 元 素 。 上 述 限 制 也 适 用 于 线 程 池 中 的 线 程 和 手 动 创 建 的 辅 助 线 程 。<br />

在 正 确 的 线 程 中 调 用 控 件<br />

有 关 控 件 的 限 制 看 起 来 似 乎 对 多 线 程 编 程 非 常 不 利 。 如 果 在 辅 助 线 程 中 运 行 的 某 个 缓 慢 操 作 不 对 UI<br />

产 生 任 何 影 响 , 用 户 如 何 知 道 它 的 进 行 情 况 呢 ? 至 少 , 用 户 如 何 知 道 工 作 何 时 完 成 或 者 是 否 出 现 错<br />

误 ? 幸 运 的 是 , 虽 然 此 限 制 的 存 在 会 造 成 不 便 , 但 并 非 不 可 逾 越 。 有 多 种 方 式 可 以 从 辅 助 线 程 获 取 消<br />

息 , 并 将 该 消 息 传 递 给 UI 线 程 。 理 论 上 讲 , 可 以 使 用 低 级 的 同 步 原 理 和 池 化 技 术 来 生 成 自 己 的 机 制 ,<br />

但 幸 运 的 是 , 因 为 有 一 个 以 Control 类 的 Invoke 方 法 形 式 存 在 的 解 决 方 案 , 所 以 不 需 要 借 助 于 如 此<br />

低 级 的 工 作 方 式 。<br />

Invoke 方 法 是 Control 类 中 少 数 几 个 有 文 档 记 录 的 线 程 规 则 例 外 之 一 : 它 始 终 可 以 对 来 自 任 何 线 程<br />

的 Control 进 行 Invoke 调 用 。Invoke 方 法 本 身 只 是 简 单 地 携 带 委 托 以 及 可 选 的 参 数 列 表 , 并 在 UI<br />

线 程 中 为 您 调 用 委 托 , 而 不 考 虑 Invoke 调 用 是 由 哪 个 线 程 发 出 的 。 实 际 上 , 为 控 件 获 取 任 何 方 法 以<br />

在 正 确 的 线 程 上 运 行 非 常 简 单 。 但 应 该 注 意 , 只 有 在 UI 线 程 当 前 未 受 到 阻 塞 时 , 这 种 机 制 才 有 效 —<br />

调 用 只 有 在 UI 线 程 准 备 处 理 用 户 输 入 时 才 能 通 过 。 从 不 阻 塞 UI 线 程 还 有 另 一 个 好 理 由 。Invoke 方<br />

法 会 进 行 测 试 以 了 解 调 用 线 程 是 否 就 是 UI 线 程 。 如 果 是 , 它 就 直 接 调 用 委 托 。 否 则 , 它 将 安 排 线 程<br />

切 换 , 并 在 UI 线 程 上 调 用 委 托 。 无 论 是 哪 种 情 况 , 委 托 所 包 装 的 方 法 都 会 在 UI 线 程 中 运 行 , 并 且<br />

只 有 当 该 方 法 完 成 时 ,Invoke 才 会 返 回 。


Control 类 也 支 持 异 步 版 本 的 Invoke, 它 会 立 即 返 回 并 安 排 该 方 法 以 便 在 将 来 某 一 时 间 在 UI 线 程 上<br />

运 行 。 这 称 为 BeginInvoke, 它 与 异 步 委 托 调 用 很 相 似 , 与 委 托 的 明 显 区 别 在 于 , 该 调 用 以 异 步 方 式<br />

在 线 程 池 的 某 个 线 程 上 运 行 , 然 而 在 此 处 , 它 以 异 步 方 式 在 UI 线 程 上 运 行 。 实 际 上 ,Control 的<br />

Invoke、BeginInvoke 和 EndInvoke 方 法 , 以 及 InvokeRequired 属 性 都 是 ISynchronizeInvoke 接<br />

口 的 成 员 。 该 接 口 可 由 任 何 需 要 控 制 其 事 件 传 递 方 式 的 类 实 现 。<br />

由 于 BeginInvoke 不 容 易 造 成 死 锁 , 所 以 尽 可 能 多 用 该 方 法 ; 而 少 用 Invoke 方 法 。 因 为 Invoke 是<br />

同 步 的 , 所 以 它 会 阻 塞 辅 助 线 程 , 直 到 UI 线 程 可 用 。 但 是 如 果 UI 线 程 正 在 等 待 辅 助 线 程 执 行 某 操<br />

作 , 情 况 会 怎 样 呢 ? 应 用 程 序 会 死 锁 。BeginInvoke 从 不 等 待 UI 线 程 , 因 而 可 以 避 免 这 种 情 况 。<br />

现 在 , 我 要 回 顾 一 下 前 面 所 展 示 的 代 码 片 段 的 合 法 版 本 。 首 先 , 必 须 将 一 个 委 托 传 递 给 Control 的<br />

BeginInvoke 方 法 , 以 便 可 以 在 UI 线 程 中 运 行 对 线 程 敏 感 的 代 码 。 这 意 味 着 应 该 将 该 代 码 放 在 它 自<br />

己 的 方 法 中 , 如 图 3 所 示 。 一 旦 辅 助 线 程 完 成 缓 慢 的 工 作 后 , 它 就 会 调 用 Label 中 的 BeginInvoke,<br />

以 便 在 其 UI 线 程 上 运 行 某 段 代 码 。 通 过 这 样 , 它 可 以 更 新 用 户 界 面 。<br />

包 装 Control.Invoke<br />

虽 然 图 3 中 的 代 码 解 决 了 这 个 问 题 , 但 它 相 当 繁 琐 。 如 果 辅 助 线 程 希 望 在 结 束 时 提 供 更 多 的 反 馈 信<br />

息 , 而 不 是 简 单 地 给 出 “Finished!” 消 息 , 则 BeginInvoke 过 于 复 杂 的 使 用 方 法 会 令 人 生 畏 。 为 了 传 达<br />

其 他 消 息 , 例 如 “ 正 在 处 理 ”、“ 一 切 顺 利 ” 等 等 , 需 要 设 法 向 UpdateUI 函 数 传 递 一 个 参 数 。 可 能 还 需<br />

要 添 加 一 个 进 度 栏 以 提 高 反 馈 能 力 。 这 么 多 次 调 用 BeginInvoke 可 能 导 致 辅 助 线 程 受 该 代 码 支 配 。<br />

这 样 不 仅 会 造 成 不 便 , 而 且 考 虑 到 辅 助 线 程 与 UI 的 协 调 性 , 这 样 设 计 也 不 好 。 对 这 些 进 行 分 析 之 后 ,<br />

我 们 认 为 包 装 函 数 可 以 解 决 这 两 个 问 题 , 如 图 4 所 示 。<br />

ShowProgress 方 法 对 将 调 用 引 向 正 确 线 程 的 工 作 进 行 封 装 。 这 意 味 着 辅 助 线 程 代 码 不 再 担 心 需 要 过<br />

多 关 注 UI 细 节 , 而 只 要 定 期 调 用 ShowProgress 即 可 。 请 注 意 , 我 定 义 了 自 己 的 方 法 , 该 方 法 违 背<br />

了 “ 必 须 在 UI 线 程 上 进 行 调 用 ” 这 一 规 则 , 因 为 它 进 而 只 调 用 不 受 该 规 则 约 束 的 其 他 方 法 。 这 种 技 术


会 引 出 一 个 较 为 常 见 的 话 题 : 为 什 么 不 在 控 件 上 编 写 公 共 方 法 呢 ( 这 些 方 法 记 录 为 UI 线 程 规 则 的 例<br />

外 )?<br />

刚 好 Control 类 为 这 样 的 方 法 提 供 了 一 个 有 用 的 工 具 。 如 果 我 提 供 一 个 设 计 为 可 从 任 何 线 程 调 用 的 公<br />

共 方 法 , 则 完 全 有 可 能 某 人 会 从 UI 线 程 调 用 这 个 方 法 。 在 这 种 情 况 下 , 没 必 要 调 用 BeginInvoke,<br />

因 为 我 已 经 处 于 正 确 的 线 程 中 。 调 用 Invoke 完 全 是 浪 费 时 间 和 资 源 , 不 如 直 接 调 用 适 当 的 方 法 。 为<br />

了 避 免 这 种 情 况 ,Control 类 将 公 开 一 个 称 为 InvokeRequired 的 属 性 。 这 是 “ 只 限 UI 线 程 ” 规 则 的<br />

另 一 个 例 外 。 它 可 从 任 何 线 程 读 取 , 如 果 调 用 线 程 是 UI 线 程 , 则 返 回 假 , 其 他 线 程 则 返 回 真 。 这 意<br />

味 着 我 可 以 按 以 下 方 式 修 改 包 装 :<br />

public void ShowProgress(string msg, int percentDone) {<br />

if (InvokeRequired) {<br />

// As before<br />

...<br />

} else {<br />

// We're already on the UI thread just<br />

// call straight through.<br />

UpdateUI(this, new MyProgressEvents(msg,<br />

PercentDone));<br />

}<br />

}<br />

图 4


ShowProgress 现 在 可 以 记 录 为 可 从 任 何 线 程 调 用 的 公 共 方 法 。 这 并 没 有 消 除 复 杂 性 — 执 行<br />

BeginInvoke 的 代 码 依 然 存 在 , 它 还 占 有 一 席 之 地 。 不 幸 的 是 , 没 有 简 单 的 方 法 可 以 完 全 摆 脱 它 。<br />

锁 定<br />

任 何 并 发 系 统 都 必 须 面 对 这 样 的 事 实 , 即 , 两 个 线 程 可 能 同 时 试 图 使 用 同 一 块 数 据 。 有 时 这 并 不 是 问<br />

题 — 如 果 多 个 线 程 在 同 一 时 间 试 图 读 取 某 个 对 象 中 的 某 个 字 段 , 则 不 会 有 问 题 。 然 而 , 如 果 有 线 程<br />

想 要 修 改 该 数 据 , 就 会 出 现 问 题 。 如 果 线 程 在 读 取 时 刚 好 另 一 个 线 程 正 在 写 入 , 则 读 取 线 程 有 可 能 会<br />

看 到 虚 假 值 。 如 果 两 个 线 程 在 同 一 时 间 、 在 同 一 个 位 置 执 行 写 入 操 作 , 则 在 同 步 写 入 操 作 发 生 之 后 ,<br />

所 有 从 该 位 置 读 取 数 据 的 线 程 就 有 可 能 看 到 一 堆 垃 圾 数 据 。 虽 然 这 种 行 为 只 在 特 定 情 况 下 才 会 发 生 ,<br />

读 取 操 作 甚 至 不 会 与 写 入 操 作 发 生 冲 突 , 但 是 数 据 可 以 是 两 次 写 入 结 果 的 混 加 , 并 保 持 错 误 结 果 直 到<br />

下 一 次 写 入 值 为 止 。 为 了 避 免 这 种 问 题 , 必 须 采 取 措 施 来 确 保 一 次 只 有 一 个 线 程 可 以 读 取 或 写 入 某 个<br />

对 象 的 状 态 。<br />

防 止 这 些 问 题 出 现 所 采 用 的 方 式 是 , 使 用 运 行 时 的 锁 定 功 能 。C# 可 以 让 您 利 用 这 些 功 能 、 通 过 锁 定<br />

关 键 字 来 保 护 代 码 (Visual Basic 也 有 类 似 构 造 , 称 为 SyncLock)。 规 则 是 , 任 何 想 要 在 多 个 线 程<br />

中 调 用 其 方 法 的 对 象 在 每 次 访 问 其 字 段 时 ( 不 管 是 读 取 还 是 写 入 ) 都 应 该 使 用 锁 定 构 造 。 例 如 , 请 参<br />

见 图 5。<br />

// This field could be modified and read on any thread, so all access<br />

// must be protected by locking the object.<br />

private double myPosition;<br />

•••<br />

public double Position {<br />

get {


Position could be modified from any thread, so we need to lock<br />

// this object to make sure we get a consistent value.<br />

lock (this) {<br />

return myPosition;<br />

}<br />

}<br />

set {<br />

lock (this) {<br />

myPosition = value;<br />

}<br />

}<br />

}<br />

public void MoveBy(double offset) {<br />

// Here we are reading, checking and then modifying the value. It is<br />

// vitally important that this entire sequence is protected by a<br />

// single lock block.<br />

lock (this) {<br />

double newPos = Position + offset;


Check within range - MINPOS and MAXPOS<br />

// will be const values defined somewhere in<br />

// this class<br />

if (newPos > MAXPOS) newPos = MAXPOS;<br />

else if (newPos < MINPOS) newPos = MINPOS;<br />

Position = newPos;<br />

}<br />

}<br />

锁 定 构 造 的 工 作 方 式 是 : 公 共 语 言 运 行 库 (CLR) 中 的 每 个 对 象 都 有 一 个 与 之 相 关 的 锁 , 任 何 线 程 均<br />

可 获 得 该 锁 , 但 每 次 只 能 有 一 个 线 程 拥 有 它 。 如 果 某 个 线 程 试 图 获 取 另 一 个 线 程 已 经 拥 有 的 锁 , 那 么<br />

它 必 须 等 待 , 直 到 拥 有 该 锁 的 线 程 将 锁 释 放 为 止 。C# 锁 定 构 造 会 获 取 该 对 象 锁 ( 如 果 需 要 , 要 先 等<br />

待 另 一 个 线 程 利 用 它 完 成 操 作 ), 并 保 留 到 大 括 号 中 的 代 码 退 出 为 止 。 如 果 执 行 语 句 运 行 到 块 结 尾 ,<br />

该 锁 就 会 被 释 放 , 并 从 块 中 部 返 回 , 或 者 抛 出 在 块 中 没 有 捕 捉 到 的 异 常 。<br />

请 注 意 ,MoveBy 方 法 中 的 逻 辑 受 同 样 的 锁 语 句 保 护 。 当 所 做 的 修 改 比 简 单 的 读 取 或 写 入 更 复 杂 时 ,<br />

整 个 过 程 必 须 由 单 独 的 锁 语 句 保 护 。 这 也 适 用 于 对 多 个 字 段 进 行 更 新 — 在 对 象 处 于 一 致 状 态 之 前 ,<br />

一 定 不 能 释 放 该 锁 。 如 果 该 锁 在 更 新 状 态 的 过 程 中 释 放 , 则 其 他 线 程 也 许 能 够 获 得 它 并 看 到 不 一 致 状<br />

态 。 如 果 您 已 经 拥 有 一 个 锁 , 并 调 用 一 个 试 图 获 取 该 锁 的 方 法 , 则 不 会 导 致 问 题 出 现 , 因 为 单 独 线 程<br />

允 许 多 次 获 得 同 一 个 锁 。 对 于 需 要 锁 定 以 保 护 对 字 段 的 低 级 访 问 和 对 字 段 执 行 的 高 级 操 作 的 代 码 , 这<br />

非 常 重 要 。MoveBy 使 用 Position 属 性 , 它 们 同 时 获 得 该 锁 。 只 有 最 外 面 的 锁 阻 塞 完 成 后 , 该 锁 才<br />

会 恰 当 地 释 放 。<br />

对 于 需 要 锁 定 的 代 码 , 必 须 严 格 进 行 锁 定 。 稍 有 疏 漏 , 便 会 功 亏 一 篑 。 如 果 一 个 方 法 在 没 有 获 取 对 象<br />

锁 的 情 况 下 修 改 状 态 , 则 其 余 的 代 码 在 使 用 它 之 前 即 使 小 心 地 锁 定 对 象 也 是 徒 劳 。 同 样 , 如 果 一 个 线<br />

程 在 没 有 事 先 获 得 锁 的 情 况 下 试 图 读 取 状 态 , 则 它 可 能 读 取 到 不 正 确 的 值 。 运 行 时 无 法 进 行 检 查 来 确<br />

保 多 线 程 代 码 正 常 运 行 。


图<br />

死 锁<br />

锁 是 确 保 多 线 程 代 码 正 常 运 行 的 基 本 条 件 , 即 使 它 们 本 身 也 会 引 入 新 的 风 险 。 在 另 一 个 线 程 上 运 行 代<br />

码 的 最 简 单 方 式 是 , 使 用 异 步 委 托 调 用 ( 请 参 见 145H 6)。<br />

public class Foo {<br />

public void CallBar() {<br />

lock (this) {<br />

Bar myBar = new Bar ();<br />

myBar.BarWork(this);<br />

}<br />

}<br />

// This will be called back on a worker thread<br />

public void FooWork() {<br />

lock (this) {<br />

// do some work<br />

•••<br />

}<br />

}


}<br />

public class Bar {<br />

public void BarWork(Foo myFoo) {<br />

// Call Foo on different thread via delegate.<br />

MethodInvoker mi = new MethodInvoker(<br />

myFoo.FooWork);<br />

IAsyncResult ar = mi.BeginInvoke(null, null);<br />

// do some work<br />

•••<br />

// Now wait for delegate call to complete (DEADLOCK!)<br />

mi.EndInvoke(ar);<br />

}<br />

}<br />

如 果 曾 经 调 用 过 Foo 的 CallBar 方 法 , 则 这 段 代 码 会 慢 慢 停 止 运 行 。CallBar 方 法 将 获 得 Foo 对 象<br />

上 的 锁 , 并 直 到 BarWork 返 回 后 才 释 放 它 。 然 后 ,BarWork 使 用 异 步 委 托 调 用 , 在 某 个 线 程 池 线 程<br />

中 调 用 Foo 对 象 的 FooWork 方 法 。 接 下 来 , 它 会 在 调 用 委 托 的 EndInvoke 方 法 前 执 行 一 些 其 他 操<br />

作 。EndInvoke 将 等 待 辅 助 线 程 完 成 , 但 辅 助 线 程 却 被 阻 塞 在 FooWork 中 。 它 也 试 图 获 取 Foo 对<br />

象 的 锁 , 但 锁 已 被 CallBar 方 法 持 有 。 所 以 ,FooWork 会 等 待 CallBar 释 放 锁 , 但 CallBar 也 在 等<br />

待 BarWork 返 回 。 不 幸 的 是 ,BarWork 将 等 待 FooWork 完 成 , 所 以 FooWork 必 须 先 完 成 , 它 才<br />

能 开 始 。 结 果 , 没 有 线 程 能 够 进 行 下 去 。


这 就 是 一 个 死 锁 的 例 子 , 其 中 有 两 个 或 更 多 线 程 都 被 阻 塞 以 等 待 对 方 进 行 。 这 里 的 情 形 和 标 准 死 锁 情<br />

况 还 是 有 些 不 同 , 后 者 通 常 包 括 两 个 锁 。 这 表 明 如 果 有 某 个 因 果 性 ( 过 程 调 用 链 ) 超 出 线 程 界 限 , 就<br />

会 发 生 死 锁 , 即 使 只 包 括 一 个 锁 !Control.Invoke 是 一 种 跨 线 程 调 用 过 程 的 方 法 , 这 是 个 不 争 的 重 要<br />

事 实 。BeginInvoke 不 会 遇 到 这 样 的 问 题 , 因 为 它 并 不 会 使 因 果 性 跨 线 程 。 实 际 上 , 它 会 在 某 个 线 程<br />

池 线 程 中 启 动 一 个 全 新 的 因 果 性 , 以 允 许 原 有 的 那 个 独 立 进 行 。 然 而 , 如 果 保 留 BeginInvoke 返 回<br />

的 IAsyncResult, 并 用 它 调 用 EndInvoke, 则 又 会 出 现 问 题 , 因 为 EndInvoke 实 际 上 已 将 两 个 因 果<br />

性 合 二 为 一 。 避 免 这 种 情 况 的 最 简 单 方 法 是 , 当 持 有 一 个 对 象 锁 时 , 不 要 等 待 跨 线 程 调 用 完 成 。 要 确<br />

保 这 一 点 , 应 该 避 免 在 锁 语 句 中 调 用 Invoke 或 EndInvoke。 其 结 果 是 , 当 持 有 一 个 对 象 锁 时 , 将 无<br />

需 等 待 其 他 线 程 完 成 某 操 作 。 要 坚 持 这 个 规 则 , 说 起 来 容 易 做 起 来 难 。<br />

在 检 查 代 码 的 BarWork 时 , 它 是 否 在 锁 语 句 的 作 用 域 内 并 不 明 显 , 因 为 在 该 方 法 中 并 没 有 锁 语 句 。<br />

出 现 这 个 问 题 的 唯 一 原 因 是 BarWork 调 用 自 Foo.CallBar 方 法 的 锁 语 句 。 这 意 味 着 只 有 确 保 正 在 调<br />

用 的 函 数 并 不 拥 有 锁 时 , 调 用 Control.Invoke 或 EndIn-voke 才 是 安 全 的 。 对 于 非 私 有 方 法 而 言 ,<br />

确 保 这 一 点 并 不 容 易 , 所 以 最 佳 规 则 是 , 根 本 不 调 用 Control.Invoke 和 EndInvoke。 这 就 是 为 什 么 “ 启<br />

动 后 就 不 管 ” 的 编 程 风 格 更 可 取 的 原 因 , 也 是 为 什 么 Control.BeginInvoke 解 决 方 案 通 常 比<br />

Control.Invoke 解 决 方 案 好 的 原 因 。<br />

有 时 候 除 了 破 坏 规 则 别 无 选 择 , 这 种 情 况 下 就 需 要 仔 细 严 格 地 分 析 。 但 只 要 可 能 , 在 持 有 锁 时 就 应 该<br />

避 免 阻 塞 , 因 为 如 果 不 这 样 , 死 锁 就 难 以 消 除 。<br />

使 其 简 单<br />

如 何 既 从 多 线 程 获 益 最 大 , 又 不 会 遇 到 困 扰 并 发 代 码 的 棘 手 错 误 呢 ? 如 果 提 高 的 UI 响 应 速 度 仅 仅 是<br />

使 程 序 时 常 崩 溃 , 那 么 很 难 说 是 改 善 了 用 户 体 验 。 大 部 分 在 多 线 程 代 码 中 普 遍 存 在 的 问 题 都 是 由 要 一<br />

次 运 行 多 个 操 作 的 固 有 复 杂 性 导 致 的 , 这 是 因 为 大 多 数 人 更 善 于 思 考 连 续 过 程 而 非 并 发 过 程 。 通 常 ,<br />

最 好 的 解 决 方 案 是 使 事 情 尽 可 能 简 单 。


UI 代 码 的 性 质 是 : 它 从 外 部 资 源 接 收 事 件 , 如 用 户 输 入 。 它 会 在 事 件 发 生 时 对 其 进 行 处 理 , 但 却 将<br />

大 部 分 时 间 花 在 了 等 待 事 件 的 发 生 。 如 果 可 以 构 造 辅 助 线 程 和 UI 线 程 之 间 的 通 信 , 使 其 适 合 该 模 型 ,<br />

则 未 必 会 遇 到 这 么 多 问 题 , 因 为 不 会 再 有 新 的 东 西 引 入 。 我 是 这 样 使 事 情 简 单 化 的 : 将 辅 助 线 程 视 为<br />

另 一 个 异 步 事 件 源 。 如 同 Button 控 件 传 递 诸 如 Click 和 MouseEnter 这 样 的 事 件 , 可 以 将 辅 助 线 程<br />

视 为 传 递 事 件 ( 如 ProgressUpdate 和 WorkComplete) 的 某 物 。 只 是 简 单 地 将 这 看 作 一 种 类 比 , 还<br />

是 真 正 将 辅 助 对 象 封 装 在 一 个 类 中 , 并 按 这 种 方 式 公 开 适 当 的 事 件 , 这 完 全 取 决 于 您 。 后 一 种 选 择 可<br />

能 需 要 更 多 的 代 码 , 但 会 使 用 户 界 面 代 码 看 起 来 更 加 统 一 。 不 管 哪 种 情 况 , 都 需 要<br />

Control.BeginInvoke 在 正 确 的 线 程 上 传 递 这 些 事 件 。<br />

对 于 辅 助 线 程 , 最 简 单 的 方 式 是 将 代 码 编 写 为 正 常 顺 序 的 代 码 块 。 但 如 果 想 要 使 用 刚 才 介 绍 的 “ 将 辅<br />

助 线 程 作 为 事 件 源 ” 模 型 , 那 又 该 如 何 呢 ? 这 个 模 型 非 常 适 用 , 但 它 对 该 代 码 与 用 户 界 面 的 交 互 提 出<br />

了 限 制 : 这 个 线 程 只 能 向 UI 发 送 消 息 , 并 不 能 向 它 提 出 请 求 。<br />

例 如 , 让 辅 助 线 程 中 途 发 起 对 话 以 请 求 完 成 结 果 需 要 的 信 息 将 非 常 困 难 。 如 果 确 实 需 要 这 样 做 , 也 最<br />

好 是 在 辅 助 线 程 中 发 起 这 样 的 对 话 , 而 不 要 在 主 UI 线 程 中 发 起 。 该 约 束 是 有 利 的 , 因 为 它 将 确 保 有<br />

一 个 非 常 简 单 且 适 用 于 两 线 程 间 通 信 的 模 型 — 在 这 里 简 单 是 成 功 的 关 键 。 这 种 开 发 风 格 的 优 势 在<br />

于 , 在 等 待 另 一 个 线 程 时 , 不 会 出 现 线 程 阻 塞 。 这 是 避 免 死 锁 的 有 效 策 略 。<br />

private void btnRead_Click(object o, EventArgs e)<br />

{<br />

btnRead.Enabled = false;<br />

txtDir.Enabled = false;<br />

Cursor = Cursors.AppStarting;<br />

// Clear the list view<br />

lvwDirList.Clear();<br />

// This could be slow, so do this via an async delegate.


ReadDelegate dlg = new ReadDelegate(ReadDirectory);<br />

dlg.BeginInvoke(txtDir.Text, null, null);<br />

}<br />

// Worker function (and associated delegate)<br />

private delegate void ReadDelegate(string dirName);<br />

private void ReadDirectory(string dirName)<br />

{<br />

try<br />

{<br />

string[] names = Directory.GetFileSystemEntries(dirName);<br />

// Marshal the results back onto the right thread.<br />

object[] pList = { names };<br />

BeginInvoke(new DirRead(readDir_DirEntries), pList);<br />

}<br />

catch (DirectoryNotFoundException)<br />

{<br />

// Raise the directory not found 'event'<br />

BeginInvoke(new MethodInvoker(readDir_DirNotFound));<br />

}


catch (Exception e)<br />

{<br />

// Raise the miscellaneous error 'event'.<br />

object[] pList = { e };<br />

BeginInvoke(new DirError(readDir_Error), pList);<br />

}<br />

}<br />

// Functions called via Invoke by worker thread.<br />

private delegate void DirRead(string[] names);<br />

private void readDir_DirEntries(string[] names)<br />

{<br />

Cursor = Cursors.WaitCursor;<br />

foreach(string name in names)<br />

{<br />

lvwDirList.Items.Add(name);<br />

}<br />

ResetCtls();<br />

}


private void readDir_DirNotFound()<br />

{<br />

MessageBox.Show(this, "Directory not found", "Error");<br />

ResetCtls();<br />

}<br />

private delegate void DirError(Exception e);<br />

private void readDir_Error(Exception e)<br />

{<br />

MessageBox.Show(this, string.Format("Error reading directory:\n{0}",<br />

e.Message), "Error");<br />

ResetCtls();<br />

}<br />

// Set the relevant controls and cursors back to their default<br />

// 'ready' states after the operation has completed.<br />

private void ResetCtls()<br />

{<br />

// Re-enable the Read button and put the cursor back to normal.<br />

btnRead.Enabled = true;<br />

txtDir.Enabled = true;


Cursor = Cursors.Default;<br />

}<br />

图 7 显 示 了 使 用 异 步 委 托 调 用 以 在 辅 助 线 程 中 执 行 可 能 较 慢 的 操 作 ( 读 取 某 个 目 录 的 内 容 ), 然 后<br />

将 结 果 显 示 在 UI 上 。 它 还 不 至 于 使 用 高 级 事 件 语 法 , 但 是 该 调 用 确 实 是 以 与 处 理 事 件 ( 如 单 击 ) 非<br />

常 相 似 的 方 式 来 处 理 完 整 的 辅 助 代 码 。<br />

取 消<br />

前 面 示 例 所 带 来 的 问 题 是 , 要 取 消 操 作 只 能 通 过 退 出 整 个 应 用 程 序 实 现 。 虽 然 在 读 取 某 个 目 录 时 UI<br />

仍 然 保 持 迅 速 响 应 , 但 由 于 在 当 前 操 作 完 成 之 前 程 序 将 禁 用 相 关 按 钮 , 所 以 用 户 无 法 查 看 另 一 个 目 录 。<br />

如 果 试 图 读 取 的 目 录 是 在 一 台 刚 好 没 有 响 应 的 远 程 机 器 上 , 这 就 很 不 幸 , 因 为 这 样 的 操 作 需 要 很 长 时<br />

间 才 会 超 时 。<br />

要 取 消 一 个 操 作 也 比 较 困 难 , 尽 管 这 取 决 于 怎 样 才 算 取 消 。 一 种 可 能 的 理 解 是 “ 停 止 等 待 这 个 操 作 完<br />

成 , 并 继 续 另 一 个 操 作 。” 这 实 际 上 是 抛 弃 进 行 中 的 操 作 , 并 忽 略 最 终 完 成 时 可 能 产 生 的 后 果 。 对 于<br />

当 前 示 例 , 这 是 最 好 的 选 择 , 因 为 当 前 正 在 处 理 的 操 作 ( 读 取 目 录 内 容 ) 是 通 过 调 用 一 个 阻 塞 API 来<br />

执 行 的 , 取 消 它 没 有 关 系 。 但 即 使 是 如 此 松 散 的 “ 假 取 消 ” 也 需 要 进 行 大 量 工 作 。 如 果 决 定 启 动 新 的 读<br />

取 操 作 而 不 等 待 原 来 的 操 作 完 成 , 则 无 法 知 道 下 一 个 接 收 到 的 通 知 是 来 自 这 两 个 未 处 理 请 求 中 的 哪 一<br />

个 。<br />

支 持 取 消 在 辅 助 线 程 中 运 行 的 请 求 的 唯 一 方 式 是 , 提 供 与 每 个 请 求 相 关 的 某 种 调 用 对 象 。 最 简 单 的 做<br />

法 是 将 它 作 为 一 个 Cookie, 由 辅 助 线 程 在 每 次 通 知 时 传 递 , 允 许 UI 线 程 将 事 件 与 请 求 相 关 联 。 通<br />

过 简 单 的 身 份 比 较 ( 参 见 图 8),UI 代 码 就 可 以 知 道 事 件 是 来 自 当 前 请 求 , 还 是 来 自 早 已 废 弃 的 请<br />

求 。<br />

// Member variable holding the current request—gets updated with the


current call object every time we start a new operation. (This call<br />

// object would also be made available to the worker function—either as a<br />

// parameter, or possibly by making the worker function a member of the<br />

// call object.)<br />

private object currReq;<br />

•••<br />

private void workerNotify(object callObj) {<br />

// Only process this event if it's from the last request we made.<br />

// (I.e. ignore belated notifications from requests we've long since<br />

// abandoned).<br />

if (object.ReferenceEquals(callObj, currReq)) {<br />

// This is for an operation we still care about, so handle it<br />

•••<br />

}<br />

}<br />

如 果 简 单 抛 弃 就 行 , 那 固 然 很 好 , 不 过 您 可 能 想 要 做 得 更 好 。 如 果 辅 助 线 程 执 行 的 是 进 行 一 连 串 阻 塞<br />

操 作 的 复 杂 操 作 , 那 么 您 可 能 希 望 辅 助 线 程 在 最 早 的 时 机 停 止 。 否 则 , 它 可 能 会 继 续 几 分 钟 的 无 用 操<br />

作 。 在 这 种 情 况 下 , 调 用 对 象 需 要 做 的 就 不 止 是 作 为 一 个 被 动 Cookie。 它 至 少 还 需 要 维 护 一 个 标 记 ,<br />

指 明 请 求 是 否 被 取 消 。UI 可 以 随 时 设 置 这 个 标 记 , 而 辅 助 线 程 在 执 行 时 将 定 期 测 试 这 个 标 记 , 以 确<br />

定 是 否 需 要 放 弃 当 前 工 作 。


对 于 这 个 方 案 , 还 需 要 做 出 几 个 决 定 : 如 果 UI 取 消 了 操 作 , 它 是 否 要 等 待 直 到 辅 助 线 程 注 意 到 这 次<br />

取 消 ? 如 果 不 等 待 , 就 需 要 考 虑 一 个 争 用 条 件 : 有 可 能 UI 线 程 会 取 消 该 操 作 , 但 在 设 置 控 制 标 记 之<br />

前 辅 助 线 程 已 经 决 定 传 递 通 知 了 。 因 为 UI 线 程 决 定 不 等 待 , 直 到 辅 助 线 程 处 理 取 消 , 所 以 UI 线 程<br />

有 可 能 会 继 续 从 辅 助 线 程 接 收 通 知 。 如 果 辅 助 线 程 使 用 BeginInvoke 异 步 传 递 通 知 , 则 UI 甚 至 有<br />

可 能 收 到 多 个 通 知 。UI 线 程 也 可 以 始 终 按 与 “ 废 弃 ” 做 法 相 同 的 方 式 处 理 通 知 — 检 查 调 用 对 象 的 标<br />

识 并 忽 略 它 不 再 关 心 的 操 作 通 知 。 或 者 , 在 调 用 对 象 中 进 行 锁 定 并 决 不 从 辅 助 线 程 调 用 BeginInvoke<br />

以 解 决 问 题 。 但 由 于 让 UI 线 程 在 处 理 一 个 事 件 之 前 简 单 地 对 其 进 行 检 查 以 确 定 是 否 有 用 也 比 较 简<br />

单 , 所 以 使 用 该 方 法 碰 到 的 问 题 可 能 会 更 少 。<br />

程 序 关 闭<br />

某 些 情 况 下 , 可 以 采 用 “ 启 动 后 就 不 管 ” 的 异 步 操 作 , 而 不 需 要 其 他 复 杂 要 求 来 使 操 作 可 取 消 。 然 而 ,<br />

即 使 用 户 界 面 不 要 求 取 消 , 有 可 能 还 是 需 要 实 现 这 项 功 能 以 使 程 序 可 以 彻 底 关 闭 。<br />

当 应 用 程 序 退 出 时 , 如 果 由 线 程 池 创 建 的 辅 助 线 程 还 在 运 行 , 则 这 些 线 程 会 被 终 止 。 终 止 是 简 单 粗 暴<br />

的 操 作 , 因 为 关 闭 甚 至 会 绕 开 任 何 还 起 作 用 的 Finally 块 。 如 果 异 步 操 作 执 行 的 某 些 工 作 不 应 该 以 这<br />

种 方 式 被 打 断 , 则 必 须 确 保 在 关 闭 之 前 这 样 的 操 作 已 经 完 成 。 此 类 操 作 可 能 包 括 对 文 件 执 行 的 写 入 操<br />

作 , 但 由 于 突 然 中 断 后 , 文 件 可 能 被 破 坏 。<br />

一 种 解 决 办 法 是 创 建 自 己 的 线 程 , 而 不 用 来 自 辅 助 线 程 池 的 线 程 , 这 样 就 自 然 会 避 开 使 用 异 步 委 托 调<br />

用 。 这 样 , 即 使 主 线 程 关 闭 , 应 用 程 序 也 会 等 到 您 的 线 程 退 出 后 才 终 止 。System.Threading.Thread 类<br />

有 一 个 IsBackground 属 性 可 以 控 制 这 种 行 为 。 它 默 认 为 false, 这 种 情 况 下 ,CLR 会 等 到 所 有 非 背<br />

景 线 程 都 退 出 后 才 正 常 终 止 应 用 程 序 。 然 而 , 这 会 带 来 另 一 个 问 题 , 因 为 应 用 程 序 挂 起 时 间 可 能 会 比<br />

您 预 期 的 长 。 窗 口 都 关 闭 了 , 但 进 程 仍 在 运 行 。 这 也 许 不 是 个 问 题 。 如 果 应 用 程 序 只 是 因 为 要 进 行 一<br />

些 清 理 工 作 才 比 正 常 情 况 挂 起 更 长 时 间 , 那 没 问 题 。 另 一 方 面 , 如 果 应 用 程 序 在 用 户 界 面 关 闭 后 还 挂<br />

起 几 分 钟 甚 至 几 小 时 , 那 就 不 可 接 受 了 。 例 如 , 如 果 它 仍 然 保 持 某 些 文 件 打 开 , 则 可 能 妨 碍 用 户 稍 后<br />

重 启 该 应 用 程 序 。


最 佳 方 法 是 , 如 果 可 能 , 通 常 应 该 编 写 自 己 的 异 步 操 作 以 便 可 以 将 其 迅 速 取 消 , 并 在 关 闭 应 用 程 序 之<br />

前 等 待 所 有 未 完 成 的 操 作 完 成 。 这 意 味 着 您 可 以 继 续 使 用 异 步 委 托 , 同 时 又 能 确 保 关 闭 操 作 彻 底 且 及<br />

时 。<br />

错 误 处 理<br />

在 辅 助 线 程 中 出 现 的 错 误 一 般 可 以 通 过 触 发 UI 线 程 中 的 事 件 来 处 理 , 这 样 错 误 处 理 方 式 就 和 完 成 及<br />

进 程 更 新 方 式 完 全 一 样 。 因 为 很 难 在 辅 助 线 程 上 进 行 错 误 恢 复 , 所 以 最 简 单 的 策 略 就 是 让 所 有 错 误 都<br />

为 致 命 错 误 。 错 误 恢 复 的 最 佳 策 略 是 使 操 作 完 全 失 败 , 并 在 UI 线 程 上 执 行 重 试 逻 辑 。 如 果 需 要 用 户<br />

干 涉 来 修 复 造 成 错 误 的 问 题 , 简 单 的 做 法 是 给 出 恰 当 的 提 示 。<br />

AsyncUtils 类 处 理 错 误 以 及 取 消 。 如 果 操 作 抛 出 异 常 , 该 基 类 就 会 捕 捉 到 , 并 通 过 Failed 事 件 将 异<br />

常 传 递 给 UI。<br />

小 结<br />

谨 慎 地 使 用 多 线 程 代 码 可 以 使 UI 在 执 行 耗 时 较 长 的 任 务 时 不 会 停 止 响 应 , 从 而 显 著 提 高 应 用 程 序 的<br />

反 应 速 度 。 异 步 委 托 调 用 是 将 执 行 速 度 缓 慢 的 代 码 从 UI 线 程 迁 移 出 来 , 从 而 避 免 此 类 间 歇 性 无 响 应<br />

的 最 简 单 方 式 。<br />

Windows Forms Control 体 系 结 构 基 本 上 是 单 线 程 , 但 它 提 供 了 实 用 程 序 以 将 来 自 辅 助 线 程 的 调 用 封<br />

送 返 回 至 UI 线 程 。 处 理 来 自 辅 助 线 程 的 通 知 ( 不 管 是 成 功 、 失 败 还 是 正 在 进 行 的 指 示 ) 的 最 简 单 策<br />

略 是 , 以 对 待 来 自 常 规 控 件 的 事 件 ( 如 鼠 标 单 击 或 键 盘 输 入 ) 的 方 式 对 待 它 们 。 这 样 可 以 避 免 在 UI 代<br />

码 中 引 入 新 的 问 题 , 同 时 通 信 的 单 向 性 也 不 容 易 导 致 出 现 死 锁 。


有 时 需 要 让 UI 向 一 个 正 在 处 理 的 操 作 发 送 消 息 。 其 中 最 常 见 的 是 取 消 一 个 操 作 。 通 过 建 立 一 个 表 示<br />

正 在 进 行 的 调 用 的 对 象 并 维 护 由 辅 助 线 程 定 期 检 查 的 取 消 标 记 可 实 现 这 一 目 的 。 如 果 用 户 界 面 线 程 需<br />

要 等 待 取 消 被 认 可 ( 因 为 用 户 需 要 知 道 工 作 已 确 实 终 止 , 或 者 要 求 彻 底 退 出 程 序 ), 实 现 起 来 会 有 些<br />

复 杂 , 但 所 提 供 的 示 例 代 码 中 包 含 了 一 个 将 所 有 复 杂 性 封 装 在 内 的 基 类 。 派 生 类 只 需 要 执 行 一 些 必 要<br />

的 工 作 、 周 期 性 测 试 取 消 , 以 及 要 是 因 为 取 消 请 求 而 停 止 工 作 , 就 将 结 果 通 知 基 类 。<br />

相 关 文 章 请 参 阅 :<br />

Applied Microsoft .NET Framework Programming by Jeffrey Richter (Microsoft Press, 2002)<br />

Essential .NET, Volume 1: The Common Language Runtime by Don Box (Addison Wesley<br />

Professional, 2002)<br />

有 关 背 景 信 息 , 请 参 阅 :<br />

146HSafe, Simple Multithreading in Windows Forms<br />

147HA Second Look at Windows Forms Multithreading<br />

Ian Griffiths 是 一 位 专 门 从 事 .NET Windows 窗 体 应 用 程 序 的 独 立 顾 问 , 现 居 英 国 。 他 是<br />

DevelopMentor 的 一 名 讲 师 , 并 与 他 人 合 著 有 NET Windows Forms in a Nutshell (O'Reilly, 2002) 和<br />

Mastering Visual Studio .NET (O'Reilly, February 2003) 一 书 。

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!