线程(Thread)与委托(Invoke和BeginInvoke)
这几天专门玩线程与委托,到处查找资料看,渐渐明白了用法、写法和一些注意事项;
描述:
什么是进程呢?当一个程序开始运行时,它就是一个进程,进程所指包括运行中的程序和程序所使用到的内存和系统资源。而一个进程又是由多个线程所组成的,线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数。
Control.Invoke 方法 (Delegate) :在拥有此控件的基础窗口句柄的线程上执行指定的委托。
Control.BeginInvoke 方法 (Delegate) :在创建控件的基础句柄所在线程上异步执行指定委托。
Control的Invoke和BeginInvoke的参数为delegate,委托的方法是在Control的线程上执行的,也就是我们平时所说的UI线程。
何时采用简单归纳:
1、提高CPU的利用率,从而提高了程序的效率;
2、当程序运行会卡住软件界面,为了使人不用焦虑等待时,采取线程与委托来处理,从而使软件界面运行流畅;
3、处理大量数据时间较长(显示一个进度条给界面)或不需要马上得到结果反馈给软件界面时;
注意事项:线程是各自独立进行管理的,一个线程不能包含另一个线程;即:用Thread创建的线程是一个线程,Control是一个线程,这是两个独立的线程;委托则专属于Control线程;至少目前我是这样理解的,因此,初涉这个领域时不小心就会犯线程相互交涉而发生错误;更具体查看微软或网络上相关资料就不赘述了。
参考网址:
http://hi.baidu.com/jok607/blog/item/393746e72f513125b8382002.html
http://www.cnblogs.com/mashang/archive/2009/08/01/1536730.html
举例是最生动的说明,看下面例子:
要执行的操作是计算一个文本控件中有多少个字符,包含多少回车数量:
private delegate void 数字委托(int 数字);
private void 计算字数(int 回车)
{
显示控件.Text = "字数:" + 文本.TextLength.ToString() + ";";
显示控件.Text += "其中包含" + 回车.ToString() + "回车数";
}
//请注意上面方法包含Control线程,而下面方法不包含Control线程,并注释掉公共类传递参数,可自己调试。
private void 计算回车数量(object 数据)
{
int 回车 = 0;
for (int i = 0, 数量 = 数据.ToString().Length; i < 数量; i++)
{
if (数据.ToString().Contains("\n")) 回车++;
if (数据.ToString().IndexOf("\n") + 1 < 数据.ToString().Length)
数据 = 数据.ToString().Substring(数据.ToString().IndexOf("\n") + 1);
else break;
if (!数据.ToString().Contains("\n")) break;
}
this.BeginInvoke(new 数字委托(计算字数), 回车);
}
private void 按钮_Click(object sender, EventArgs e)
{
new Thread(new ParameterizedThreadStart(计算回车数量)).Start(文本控件.Text);
}
//声明并运行创建的线程,同时传递参数,Start传递的是object类型
运行正常。
在private void 计算回车数量(object 数据)方法中用委托来返回计算结果,参数为delegate,在最上一行声明,这样就返回到Control线程。
假如不用委托而直接用:计算字数(回车);将提示错误,原因就是不同线程发生交涉。如下:
private void 计算回车数量(object 数据)
{
int 回车 = 0;
for (int i = 0, 数量 = 数据.ToString().Length; i < 数量; i++)
{
if (数据.ToString().Contains("\n")) 回车++;
if (数据.ToString().IndexOf("\n") + 1 < 数据.ToString().Length)
数据 = 数据.ToString().Substring(数据.ToString().IndexOf("\n") + 1);
else break;
if (!数据.ToString().Contains("\n")) break;
} 计算字数(回车);
}
声明并运行线程语句不同写法:
--------------------------------------------------用公共类传递
public class 共类 { public string 数据 { get; set; } public int 数值 { get; set; } }
共类 文本 = new 共类(); 文本.数据 = 文本控件.Text;
Thread 计算 = new Thread(new ParameterizedThreadStart(计算回车数量)); 计算.Start(文本); 计算.Abort();
-----------------------------------------------------------------------------------------------------
string 文本 = 文本控件.Text; new Thread(delegate() { 计算回车数量(文本); }).Start();
Thread 线程 = new Thread(delegate() { 线程另存文件(); });
线程.SetApartmentState(System.Threading.ApartmentState.STA);//.MST
线程.Start();
------------------------------------------------------------------------------------
初涉的人为如何返回线程结果苦恼,采用很多种方法,我这里采用委托直接返回线程结果;
下面看看委托:一般我是这样写委托就可以了,this.BeginInvoke(new 数字委托(计算字数), 回车);
/*还看到下面的一种写法是在委托结束后回调结果的:
//此处开始异步执行,并且可以给出一个回调函数
计算.BeginInvoke(文本控件.Text, new AsyncCallback(委托回调), null);
delegate int 申明委托签名(string 传入值);
申明委托签名 计算 = new 申明委托签名(委托执行);//把委托和具体的方法关联起来
public static int 委托执行(string 文本)//委托调用的方法
{
int 回车 = 0;
for (int i = 0, 数量 = 数据.ToString().Length; i < 数量; i++)
{
if (数据.ToString().Contains("\n")) 回车++;
if (数据.ToString().IndexOf("\n") + 1 < 数据.ToString().Length)
数据 = 数据.ToString().Substring(数据.ToString().IndexOf("\n") + 1);
else break;
if (!数据.ToString().Contains("\n")) break;
} return 回车;
}
public void 委托回调(IAsyncResult 返回值)
{
this.BeginInvoke(new 数字委托(计算字数), 计算.EndInvoke(返回值));
}
*/
同样我采用委托返回结果,如果直接用:计算字数(计算.EndInvoke(返回值));将提示错误。
原因参考网址:
http://technet.microsoft.com/zh-cn/library/system.asynccallback(zh-tw).aspx
使用 AsyncCallback 委托在一个单独的线程中处理异步操作的结果。AsyncCallback 委托表示在异步操作完成时调用的回调方法。回调方法采用 IAsyncResult 参数,该参数随后可用来获取异步操作的结果。
委托注意事项:委托的方法传入参数必须对应,否则发生错误;
如:委托方法的传入参数是string则声明也必须是:private delegate void 文本委托(string 内容);
委托参数的数量必须与委托方法参数数量相等且类型必须一致;
如:委托方法的传入参数是:DateTime 日期, DateTime 预测日期, string 内容,则声明也必须对应:
private delegate void 委托(DateTime 日期, DateTime 预测日期, string 内容);
这里顺便提及是因为看到有些提问是否可以带几个参数;还有线程如何传参的,有人回复设一个公共变量来传参,提问人觉得很遗憾,各人方法不尽相同,也属正常,无可非议。
同时还应该注意:线程与异步委托完成时间是不定的,设计时也应该慎重考虑或用调试决定。
以上就是这些天专门玩线程与委托的一些经验,今天凭着思路就写了这些,知道写得不好,看了莫笑。
初学灵活变通和试验调试相对比较弱,这里再给一个直接调用多参数方法例子:
Thread 线程 = new Thread(delegate() { this.Invoke(new Action(() => 加载快捷菜单(快捷菜单, 快捷参数, 快捷事件))); });
线程.Start();
其实线程没那么难搞定,这里给个定式:
Thread 线程 = new Thread(delegate()
{
this.Invoke(new Action(() => {/*如果涉及UI线程原代码放这里,如果没有删除这句*/}));
});
线程.Start();
new Thread(delegate() { this.Invoke(new Action(delegate() { 乾坤大挪移(快捷菜单, 乾坤大挪移参数); })); }).Start();
使用匿名委托:
this.Invoke(new Action(delegate() { /*任何语句或方法*/}));
new Thread(delegate() { /*不涉及UI线程任何语句或方法*/ }).Start();
Thread 线程 = new Thread(delegate()
{/*原代码放这里就可以了*/}); 线程.Start();线程.Join();/*后续其他代码*/
有时方法外使用线程可以改为方法内使用线程(多参数传递)是一样的,下面是改动的例子:
private void 乾坤大挪移(ContextMenuStrip 菜单名, string[] 子参数)
{
Thread 线程 = new Thread(delegate()
{
this.Invoke(new Action(() =>
{
/*原代码放这里就可以了*/
}));
}); 线程.Start();
}
其实线程和委托使用起来是很方便的,特别是跨线程访问也很简单,只要是涉及到控件线程就使用委托就可以了,下面是改动的例子:
private void 时间_Tick(object sender, EventArgs e)
{
Thread 线程 = new Thread(delegate()
{
if (秒 < 59) 秒++; else { 秒 = 0; 分++; } if (分 == 60) { 分 = 0; 时++; } if (时 == 5) 时 = 0;
this.Invoke(new Action(() =>
显示时间.Text = DateTime.Parse(时.ToString("0:") + 分.ToString("00:") + 秒.ToString("00")).ToLongTimeString()));
}); 线程.Start();
}
当使用异步委托(BeginInvoke)需注意,有可能造成界面反应更忙,一般不与界面反应有关不轻易使用。
private void button1_Click(object sender, EventArgs e)
{
IAsyncResult 调用返回 = listBox1.BeginInvoke(new Action(() =>
{
Thread.Sleep(2000);
listBox1.Items.Add(Thread.CurrentThread.Name);
}));
listBox1.EndInvoke(调用返回);
listBox1.Invoke((EventHandler)delegate
{
Thread.Sleep(2000);
listBox1.Items.Add(Thread.CurrentThread.Name);//运行到这里,其实把上一个函数的睡着的异步给弄醒了
});
listBox1.BeginInvoke(new MethodInvoker(delegate
{
Thread.Sleep(2000);//调用的时候需要等待的时间,异步是指CPU自己有空闲的时候合理分配空间给予执行;跟此时间无关;
listBox1.Items.Add(Thread.CurrentThread.Name);
}));
}补充参考
Task 类
var t = Task.Factory.StartNew(() => button1_Click(null ,null ));
线程池
System.Threading.ThreadPool.QueueUserWorkItem(
new System.Threading.WaitCallback(一些长期任务));
System.Threading.ThreadPool.QueueUserWorkItem(
new System.Threading.WaitCallback(另一个长期任务),传递);
private void 一些长期任务(Object state)
{
// 插入代码来执行一项艰巨的任务。
this.Invoke(new Action(() => { resultLabel.Text = "0"; }));
int aa = 100;
do
{
System.Threading.Thread.Sleep(1000);
this.Invoke(new Action(() => { resultLabel.Text = (int.Parse(resultLabel.Text) + 1).ToString(); }));
} while (--aa > 0);
}
private void 另一个长期任务(Object 参数)
{
// 插入代码来执行一项艰巨的任务,参数包装。
string aa=参数.ToString();
}
使用组件BackgroundWorker 类
-----------------------------------------------------------------------------------
有关封装与三目运算符应用:
Func<
string
,
bool
> 逻辑 =
delegate
(
string
年信息)
{
if
(年信息.Contains(
"11"
))
return
false
;
return
true
;
/*在这里可以写多语句处理,写在调用之前*/
};
Func
{
return 隔符.Select(元素 => 数据.Contains(元素)).Any(比 => 比 == true);
};
return
(年信息.Contains(
"00"
)) ?
true
: 逻辑(年信息);
上面利用有参有返回(仅1个参数和返回值)
委托有参数无返回:
Action
{
/*在这写处理代码*/
};
调用:日期计算(new DateTime[] { 日期1, 日期2 });
委托无参有返回:
Func
{
string[] 内容 = new string[0];
/*在这写处理代码*/
return 内容;
};
下面无参无返回封装一并记录
Action 显示 =
delegate
()
{
升起提示窗体(显示事件.Text.Replace(
"\r\n"
,
""
));
};
Action 显示 = ()=>
{
};
Action
{
};
显示(/*在需要调用的地方写这行代码*/);
点击打开链接<多线程描述>
并行处理:using System.Threading.Tasks;/*并行运算*/ Parallel.Invoke(() =>{/*代码块*/});
Parallel.Invoke(new System.Action(delegate() {/*包含不适合于"雷姆达表达式的"代码块*/}));
Parallel.Invoke(delegate()
{
this.BeginInvoke(new Action(delegate()
{ /*适用嵌套循环提高速度*/}));
});
下面转来自微软代码例子:
Action
Console.WriteLine("t1 has been launched. (Main Thread={0})", Thread.CurrentThread.ManagedThreadId);
t1.Wait();
Task t3 = new Task(action, "gamma");
t3.RunSynchronously();
t3.Wait();
public Form1()
{
InitializeComponent();
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = true;
}
private void startAsyncButton_Click(object sender, EventArgs e)
{
if (backgroundWorker1.IsBusy != true)
{
// 启动异步操作。
backgroundWorker1.RunWorkerAsync();
}
}
private void cancelAsyncButton_Click(object sender, EventArgs e)
{
if (backgroundWorker1.WorkerSupportsCancellation == true)
{
// 取消异步操作。
backgroundWorker1.CancelAsync();
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 1; i <= 10; i++)
{
if (worker.CancellationPending == true)
{
e.Cancel = true;
break;
}
else
{
// 执行耗时的操作,并报告进度。
System.Threading.Thread.Sleep(500);
worker.ReportProgress(i * 10);
}
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
resultLabel.Text = (e.ProgressPercentage.ToString() + "%");
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled == true)
{
resultLabel.Text = "取消!";
}
else if (e.Error != null)
{
resultLabel.Text = "错误: " + e.Error.Message;
}
else
{
resultLabel.Text = "做!";
}
}
Public Sub 定时事件(ByVal state As Object)
Me.BeginInvoke(
New Action(
Sub()
移动字幕.Left = 移动字幕.Left - 1
If 移动字幕.Right < 0 Then 移动字幕.Left = Me.Width
End Sub)
)
End Sub
void 控件异步处理(Action 无返回方法)
{
Parallel.Invoke(delegate()
{
this.BeginInvoke(new Action(delegate()
{
无返回方法();
}));
});
}
void 背景(DataTable 数据表)
{
循环 = 数据表.Rows.Count - 1; int 列 = 数据表.Columns.Count - 1;
if (循环 > 0) while (true)
{
数据列表.Rows[循环].Cells[列].Style.BackColor = Color.Transparent;
if (--列 < 0) { if (--循环 < 0) break; 列 = 数据表.Columns.Count - 1; }
}
}
void 显示(DataTable 数据表)
{
数据列表.DataSource = null;
数据列表.DataSource = 数据表;
数据列表.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
}
if (!new System.IO.FileInfo(@System.Environment.CurrentDirectory + "\\中草药数据表.XML").Exists)
动态表格(new string[] { "名称", "别名", "性味", "归经", "主治" });
else
{
数据表加载(中草药数据表);
控件异步处理(() => 显示(中草药数据表));
控件异步处理(() => 背景(中草药数据表));
this.Text += " 已加载:" + 中草药数据表.Rows.Count + "条数据。";
}
if (new System.IO.FileInfo(@System.Environment.CurrentDirectory + "\\中草药树表.XML").Exists)
{
DataTable 中草药树表 = new DataTable("中草药树表");
数据表加载(中草药树表);
控件异步处理(() => 玄龙戏珠无级树(中草药树表));
}