ç¼ç¨èµæï¼ å¤çº¿ç¨ - é误æ示ï¼åçäºå¼å¸¸- å客å
ç¼ç¨èµæï¼ å¤çº¿ç¨ - é误æ示ï¼åçäºå¼å¸¸- å客å
ç¼ç¨èµæï¼ å¤çº¿ç¨ - é误æ示ï¼åçäºå¼å¸¸- å客å
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) 一 书 。