C# 委托 线程

Posted tlmbem

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C# 委托 线程相关的知识,希望对你有一定的参考价值。

delegate匿名方法(匿名函数)

1. 函数和方法等价。匿名方法能够让你声明一个方法体而不需要给它指定一个名字,它们以一个“普通的”方法存在,但是在你的代码中没有任何方法显式调用它。,返回参数不需要声明,会根据[语句块]决定。

2. 匿名方法只能在使用委托的时候创建,它们通过delegate关键字创建或者Lambda表达式(匿名函数)。定义方式:delegate(显式参数,显式参数) 语句块

 delegate(bool x)  return x ? 1 : 2; 

3. 匿名函数总是和委托一齐使用 :    定义委托变量=匿名函数

 Func<bool, int> fun = delegate(bool x)  return x ? 1 : 2; ;

 

Lambda表达式(匿名函数)

1. "Lambda表达式"可以是一个表达式也可以是一个匿名函数,Lambda表达式都使用Lambda运算符“=>”。Lambda运算符的左边是[输入参数(如果有)],右边是[表达式]或[语句块],返回参数不需要声明,会根据[表达式]或[语句块]决定。表达式只能有一条语句,语句块可以有多条语句。

(参数1,参数2)=>表达式或语句块 

1. () =>  return 1; ;          //无参数 =>语句块 
2. ( x) => x * 5;                //单参数,隐式类型 =>表达式 
3. ( x) =>  return x * 5; ;    //单参数,隐式类型=>语句块 
4. (int x) => x * 5;             //单参数,显式类型=>表达式 
5. (int x) =>  return x * 5; ; //单参数,显式类型=>语句块 
6. ( x, y) => x * y ;            //多参数,隐式类型=> 表达式

 2. Lambda表达式可以和委托一齐使用 :    定义委托变量=Lambda表达式

 Func<bool, int> fun = (bool x) =>  return x ? 1 : 2; ;

 

delegate委托

1. 委托的用途就是把方法当作参数来进行传递。

public delegate int MethodtDelegate(int x, int y);//定义委托
MethodtDelegate md = (x, y) =>  return x * y; ;//定义委托变量
md(1, 2);//调用委托

2. 可以用+=对一个委托变量绑定多个方法。这叫多播委托,利用 -=移除方法。

md += (x, y) =>  return x + y; ;
md += (x, y) =>  return x - y;  ;

3. C#预定义的三种泛型委托Action<>、Func<>、Predicate<>

3.1. Action<>  至少0个参数,至多16个参数,无返回值。

Action<intstringfloat> action=null;

3.2. Func<>    至少1个参数(返回值参数),至多16个参数,有返回值。最后一个参数为返回值参数

Func<intstringfloat> func=null;

3.3. Predicate<> 只有一个参数,默认返回bool。

Predicate一般用于Lambda表达式里面的条件,表示定义一组条件并确定指定对象是否符合这些条件的方法,此方法常在集合的查找中被用到。如:数组,正则拼配的结果集中被用到。

Predicate<int> predicate=null;

 

0 异步委托介绍:

Invoke 开始同步调用委托(同步调用Invoke,无论是否新开线程都会导致阻塞Invoke所在的线程)
BeginInvoke 开始异步调用委托
EndInvoke 返回异步调用的结果集(当使用BeginInvoke异步调用方法时,如果方法未执行完,EndInvoke方法就会一直阻塞,直到被调用的方法执行完毕)
AsyncCallback 结束异步调用后要执行的操作。

1. (Control的Invoke和BeginInvoke)是在Control线程上调用。所以如果在Control线程上调用Control的Invoke或BeginInvoke来进行调用某个委托方法来达到跨线程或异步是错误的。那Control的Invoke和BeginInvoke的用途是什么呢,他的用途是让其他线程的某个方法在Control所在线程上执行。如果在其他线程上调用Control的Invoke,这时候其他线程会被阻塞,直到Control线程执行完其他线程才会在继续执行。而Control的BeginInvoke不会让其他线程阻塞。

2. (Delegate的Invoke和BeginInvoke)是在线程池中调用。 所以一般如果某个方法执行时间特别长,都会用Delegate的BeginInvoke执行,这样Control线程就不会被阻塞。但要注意,如果在Control线程中调用Delegate的Invoke,虽然Delegate的Invoke是从线程池的线程同步调用,但他还是会阻塞Control线程的,所以要用Delegate的BeginInvoke才能实现异步。还有一种情况就是在线程池上用Delegate的Invoke会导致线程池上Delegate所在线程被阻塞,直到Control所在线程上执行完线程池上Delegate所在线程才能继续执行。

3. 使用BeginInvoke时特别要注意的就是BeginInvoke里面所用到的变量必须全部使用参数形式传进去,防止异步时被幕改变量值。

1 异步有阻塞:

例如有一个程序现在要分别调用 接口1和接口2,并要获取到他们两个接口的结果集后才能继续执行后面的代码。接口1调用耗时为10秒,接口2调用耗时5秒,如果是同步调用这两个接口并要处理这两个接口返回值时,一共需要耗时15秒。但如果我们使用异步同时调用这两个接口,那我们一般最多只需要10秒就可以返回两个接口的结果集。但是由于我们要在调用接口的这个方法退出前必须要获取到接口返回的结果,所以我们要用EndInvoke;这时候调用线程会处于阻塞状态。

//定义调用接口1委托
Func<string, string> getInfo1 = (string txt) =>  /*调用接口1*/ Thread.Sleep(10000); return "1"; ;
//定义调用接口2委托
Func<string, string> getInfo2 = (string txt) =>  /*调用接口2*/ Thread.Sleep(5000); return "2"; ;

//开始调用接口1
IAsyncResult ar1 = getInfo1.BeginInvoke("调用接口1", null, null);
//开始调用接口2
IAsyncResult ar2 = getInfo2.BeginInvoke("调用接口2", null, null);

string result1 = getInfo1.EndInvoke(ar1);//处理接口1返回结果
string result2 = getInfo2.EndInvoke(ar2);//处理接口2返回结果

for (int i = 0; i < 10; i++)


 

2 异步有阻塞加超时:

利用WaitOne方法,这个方法的超时设置指代码运行但这句代码时开始计算时间,所以同时有多个BeginInvoke异步调用时要注意。

//定义调用接口1委托
Func<string, string> getInfo1 = (string txt) =>  /*调用接口1*/ Thread.Sleep(1000); return "1"; ;
//定义调用接口2委托
Func<string, string> getInfo2 = (string txt) =>  /*调用接口2*/ Thread.Sleep(7000); return "2"; ;

//开始调用接口1
IAsyncResult ar1 = getInfo1.BeginInvoke("调用接口1", null, null);
//开始调用接口2
IAsyncResult ar2 = getInfo2.BeginInvoke("调用接口2", null, null);

//string result1 = getInfo1.EndInvoke(ar1);//处理接口1返回结果
//string result2 = getInfo2.EndInvoke(ar2);//处理接口2返回结果

int TimeOut = 5000;//超时时间为5秒
Stopwatch sw = new Stopwatch();
sw.Start();

if (ar1.AsyncWaitHandle.WaitOne(TimeOut))

getInfo1.EndInvoke(ar1); //方法成功执行返回值
sw.Stop();
TimeOut -= (int)sw.ElapsedMilliseconds;//减掉第一个异步的耗时

else

sw.Stop();
TimeOut = 0;
//超时


if (ar2.AsyncWaitHandle.WaitOne(TimeOut))

getInfo2.EndInvoke(ar2); //方法成功执行返回值

else

//超时


for (int i = 0; i < 10; i++)


 

3 异步无阻塞单向:

例如异步写日志,Control线程把要写入的日志信息传递给另外一个线程,不管另外一个线程的执行进度,Control线程继续执行其他工作。

//定义写日志委托
Action<string> writeLog = (string log) =>  /*写日志内容*/ ;
//在写日志委托回调函数李自动调用EndInvoke
AsyncCallback callFun = (result) =>  ((Action<string>)result.AsyncState).EndInvoke(result); ;
//开始异步写日志
writeLog.BeginInvoke("日志内容", callFun, writeLog);

 

4 异步无阻塞加回调:

例如有一个UI程序按钮现在要分别调用接口1和接口2,并要获取到他们两个接口的结果集后要更新按钮上的文字。接口调用耗时为10秒,接口2调用耗时5秒,如果是同步调用这两个接口并要处理这两个接口返回值时,一共需要耗时15秒。UI画面会卡死15秒。但如果我们使用上面1的方法,那UI界面也至少要卡死10秒。所以如果我们把EndInvoke放在一个AsyncCallback里面处理,当我们按下按钮时,会异步调用这两个接口,由于UI线程没有被阻塞,所以界面不会卡死,当等到接口被调用完成后将会自动调用AsyncCallback利用EndInvoke返回结果处理按钮上的文字。

private void asynbtn_Click(object sender, EventArgs e)

//定义调用接口1委托
Func<string, string> getInterface1 = (string txt) =>  Thread.Sleep(5000); return "1"; ;
//定义调用接口2委托
Func<string, string> getInterface2 = (string txt) =>  Thread.Sleep(7000); return "2"; ;

//异步全局信息
AsyncGlobalInfo agi = new AsyncGlobalInfo();
agi.gi1 = getInterface1;
agi.gi2 = getInterface2;

//开始调用接口1
IAsyncResult ar1 = getInterface1.BeginInvoke("调用接口1参数", new AsyncCallback((result) =>  AsyncGlobalInfo ic = (AsyncGlobalInfo)result.AsyncState; ic.result1 = ic.gi1.EndInvoke(result); ic.isCompleted1 = true; setText(ic); ), agi);
//开始调用接口2
IAsyncResult ar2 = getInterface2.BeginInvoke("调用接口2参数", new AsyncCallback((result) =>  AsyncGlobalInfo ic = (AsyncGlobalInfo)result.AsyncState; ic.result2 = ic.gi2.EndInvoke(result); ic.isCompleted2 = true; setText(ic); ), agi);

//定义处理按钮委托
public void setText(AsyncGlobalInfo infoall)

//判断所有的异步操作都完成
if (infoall.isCompleted1 && infoall.isCompleted2)

this.Invoke(new Action(() =>  this.button1.Text = infoall.result1 + " " + infoall.result2; ));



/// <summary>
/// 异步全局信息
/// </summary>
public class AsyncGlobalInfo

public Func<string, string> gi1;
public bool isCompleted1 = false;
public string result1 = null;
public Func<string, string> gi2;
public bool isCompleted2 = false;
public string result2 = null;

 

5 异步无阻塞加回调加凭据:

以上4的方法都有一个问题,那就是耗时问题。1如果这个异步调用必须在指定时间内完成,但4的方法都没有时间限制,所以有可能造成永久等待。2例如一个按钮点击了两次,如果第一次处理时间比第二次长,有可能第二次结果会被第一次覆盖。那要怎么做呢。一般添加一个凭据来验证异步调用和回调是同一次。

private string textBoxToken = "";//textBox凭据

private void asynbtn_Click(object sender, EventArgs e)

//定义调用接口1委托
Func<string, string> getInterface1 = (string txt) =>  Thread.Sleep(5000); return "1"; ;
//定义调用接口2委托
Func<string, string> getInterface2 = (string txt) =>  Thread.Sleep(5000); return "2"; ;

textBoxToken = "asynbtn";
//异步全局信息
AsyncGlobalInfo agi = new AsyncGlobalInfo();
agi.gi1 = getInterface1;
agi.gi2 = getInterface2;
agi.token = textBoxToken;

//开始调用接口1
IAsyncResult ar1 = getInterface1.BeginInvoke("调用接口1参数", new AsyncCallback((result) =>  AsyncGlobalInfo ic = (AsyncGlobalInfo)result.AsyncState; ic.result1 = ic.gi1.EndInvoke(result); ic.isCompleted1 = true; if (this.textBoxToken == ic.token) setText(ic); ), agi);
//开始调用接口2
IAsyncResult ar2 = getInterface2.BeginInvoke("调用接口2参数", new AsyncCallback((result) =>  AsyncGlobalInfo ic = (AsyncGlobalInfo)result.AsyncState; ic.result2 = ic.gi2.EndInvoke(result); ic.isCompleted2 = true; if (this.textBoxToken == ic.token) setText(ic); ), agi);


private void copybtn_Click(object sender, EventArgs e)

//定义调用接口1委托
Func<string, string> getInterface1 = (string txt) =>  Thread.Sleep(3000); return "3"; ;
//定义调用接口2委托
Func<string, string> getInterface2 = (string txt) =>  Thread.Sleep(3000); return "4"; ;

textBoxToken = "copybtn";
//异步全局信息
AsyncGlobalInfo agi = new AsyncGlobalInfo();
agi.gi1 = getInterface1;
agi.gi2 = getInterface2;
agi.token = textBoxToken;

//开始调用接口1
IAsyncResult ar1 = getInterface1.BeginInvoke("调用接口1参数", new AsyncCallback((result) =>  AsyncGlobalInfo ic = (AsyncGlobalInfo)result.AsyncState; ic.result1 = ic.gi1.EndInvoke(result); ic.isCompleted1 = true; if (this.textBoxToken == ic.token) setText(ic); ), agi);
//开始调用接口2
IAsyncResult ar2 = getInterface2.BeginInvoke("调用接口2参数", new AsyncCallback((result) =>  AsyncGlobalInfo ic = (AsyncGlobalInfo)result.AsyncState; ic.result2 = ic.gi2.EndInvoke(result); ic.isCompleted2 = true; if (this.textBoxToken == ic.token) setText(ic); ), agi);


//定义处理按钮委托
public void setText(AsyncGlobalInfo infoall)

//判断所有的异步操作都完成
if (infoall.isCompleted1 && infoall.isCompleted2)

this.Invoke(new Action(() =>  this.textBox1.Text = infoall.result1 + " " + infoall.result2; ));



/// <summary>
/// 异步全局信息
/// </summary>
public class AsyncGlobalInfo

public string token;
public Func<string, string> gi1;
public bool isCompleted1 = false;
public string result1 = null;
public Func<string, string> gi2;
public bool isCompleted2 = false;
public string result2 = null;

上面为什么阻塞的用来超时处理,无阻塞用凭据处理呢,你可以理解阻塞的是按代码顺序执行的,无阻塞类似于并发。

以上是关于C# 委托 线程的主要内容,如果未能解决你的问题,请参考以下文章

C# 委托 线程 窗体假死

C# 委托 线程

C# 使用成员函数的委托来创建新线程

C#委托,简单粗暴理解

C# Winform 多线程异步委托进度条

C# 多线程初级汇总