多线程,匿名委托,异步调用
常规经典多线程和异步
简单的线程处理 及同步:
public class MuThread
{
int a = 0;
int i = 0;
private volatile bool _run=true;
public void method(object par)
{
while (_run)
{
Console.WriteLine("运行中"+par.ToString());
}
Console.WriteLine("已结束");
}
public void stop()
{
_run = false;
}
public void method1()
{
while (a < 100)
{
a++; Console.WriteLine("线程1输出:" + a);
}
}
public void method2()
{
//当不加入线程同步代码的时候调用method1跟method2 会出现混乱
//但是通过数他们的输出行数依然是100行
//说明while判断是正确的
//当CPU分配时间片的时候 无论他们有多么接近 其实同一时间依然只有一个线程在访问变量a
//但是线程1跟2是CPU随机分配时间片交替前进的 实现同一时间多个进度协同执行任务的效果
while (a < 100)
{
a++; Console.WriteLine("线程2输出:" + a);
}
}
public void methodSyn1()
{
while (a < 100)
{
lock (this)
{
//意思是代码进入lock范围后 大括号内的代码将会被单独占用直到执行完
//而不会临时被其他时间片插入导致a的值已经更改
//当时间片继续回到此段时还沿用原来的 变量 导致混乱
//如果去掉前面的判断 会输出101 ,至于原理么跟孙鑫C++线程同步是一样的 也有Monitor.Entry()
if (a < 100)
{
a++; Console.WriteLine("线程1输出:" + a);
}
}
}
}
public void methodSyn2()
{
while (a < 100)
{
Monitor.Enter(this);
if (a < 100)
{
a++; Console.WriteLine("线程2输出:" + a);
}
Monitor.Exit(this);
}
}
}
线程示例:
static void Main()
{
//Service s = new Service();
//s.start();
//初始化MuThread的时候可以在构造函数 中传入一个方法引用也就是delegate
//然后再MuThread实例的线程方法执行完毕后调用该delegate
//这种编程方式我一直认为很高深 被那些砖家称之为"回调"callback
//参见ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.chs/dv_fxadvance/html/52b32222-e185-4f42-91a7-eaca65c0ab6d.htm
MuThread m = new MuThread();
//ThreadStart 其实是一个委托
//在C++里就需要声明委托
//为了给方法传参数但是不能为每种类型都定义一个委托 所以只能传入object类型
//.Net为我们简化了的语法
Thread tPar = new Thread(m.method);
tPar.Start("传入的参数");
//ParameterizedThreadStart委托显示语法 start()其实相当于Event.do()
ParameterizedThreadStart del = new ParameterizedThreadStart(m.method);
Thread tPar2 = new Thread(del);
tPar2.Start("传入的参数");
Thread t = new Thread(new ThreadStart(m.method1));
Thread t2 = new Thread(new ThreadStart(m.method2));
t2.Start();
t.Start();
//利用volatile 参数的状态终止线程
while (!tPar.IsAlive) ;
Thread.Sleep(100);
m.stop();
//MSDN的意思是阻断当前调用t的线程 也就是Main 主线程 直到让t执行完毕
//事实确实是这样的 当不加Join()的时候 看着t.start()的语句在main方法下面的循环语句前面
//其实他们的时间片依然由CPU随机分配的 "主线程调用"这句输出是穿插在前面子线程输出一起的
//参见ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.chs/fxref_mscorlib/html/56ed7b6f-efe0-67e7-34bc-766dd9f693f9.htm
//当主线程 也就是Main方法里进行Thread.Sleep(200)时 其实是挂起主线程 这样就可以把时间片让给那些子线程
t.Join();
for (int i = 0; i < 50; i++)
{
Console.WriteLine("主线程调用");
}
//使用线程池
//ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.chs/dv_csref/html/5426dae4-c3f3-4e76-a998-19f7ca4baf3f.htm
Console.ReadKey();
}
又反复测试了下join其实感觉就像endInvoke 就是阻止,说白了把指定的线程join到当前主线程 说白了就是阻塞操作 可不就是endInvoke么 ,到trd.Join()的 地方位置 必须要等待trd执行完毕
使用backgroundworker组件:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = true;
button3.Enabled = false;
}
private void button2_Click(object sender, EventArgs e)//start
{
textBox1.Text = "开始产生10000以内的随机数。。。\n\n";
button2.Enabled = false;
button3.Enabled = true;
//在后台开始操作
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
//不要直接使用组件实例名称(backgroundworker1)因为有多个backgroundworker时
//直接使用会产生耦合问题 应该通过下面的转换使用它
BackgroundWorker worker = sender as BackgroundWorker;
//下面的内容相当于线程要处理的内容。//注意 不要在此事件中和界面控件打交道
Random r = new Random();
int numCount = 0;
while (worker.CancellationPending==false)
{
int num = r.Next(10000);
if (num%5==0)
{
numCount++;
worker.ReportProgress(0, num);
Thread.Sleep(1000);
}
}
e.Result = numCount;
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
int num = (int)e.UserState;
textBox1.Text += num + " ";
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error==null)
{
textBox1.Text += "\n\n操作停止,共产生" + e.Result + "个能被5整除的随机数";
}
else
{
textBox1.Text += "\n操作过程中产生错误:" + e.Error;
}
}
private void button3_Click(object sender, EventArgs e)
{
backgroundWorker1.CancelAsync();
button3.Enabled = false;
button2.Enabled = true;
}
}
一些新式让人混淆的lamda匿名委托 伪协程 又像异步的东西
一些关于匿名委托 和lambda表达式的写法 ,还有异步调用:
1 public void callDel(Func<int,int,int> dd) 2 { 3 int rst = dd.Invoke(5, 2); 4 Console.WriteLine("call Func rst is:{0}", rst); 5 } 6 7 public int method1() 8 { 9 10 //delDone.WaitOne(2000); 11 Thread.Sleep(2000); 12 13 return 1; 14 } 15 16 public delegate int MyDele(); 17 ManualResetEvent delDone = new ManualResetEvent(false); 18 private void Form3_Load(object sender, EventArgs e) 19 { 20 21 //waitone的作用 仅仅是阻断当前行代码以后的部分继续执行 22 //然后在回调里证明已经回调已经完成了 继续放行 23 //实际上是一种失去异步意义的一种方式 ,线程同步 24 delDone.Reset(); 25 MyDele d4 = new MyDele(method1); 26 d4.BeginInvoke((iar) => 27 { 28 int rdmResult = d4.EndInvoke(iar); 29 Console.WriteLine("iar call back state :" + iar.AsyncState.ToString()); 30 Console.WriteLine("iar call back rst is :{0}", rdmResult); 31 delDone.Set(); 32 }, "ss"); 33 //delDone.Set(); 34 delDone.WaitOne(1000); 35 Console.WriteLine("lalalala...."); 36 37 return;//---------------------------------------------------------------------------------------------------- 38 var cl = new {name="xiang",age=21}; 39 Console.WriteLine(cl.name); 40 return;//----------------------------------------------------------------------------------------------------- 41 //调用func 调用func 的直接好处就是 让你连委托声明 那句都省了 42 //说白了就是你传一段代码进来 我来执行 ,那么你传的这段代码就是参数 。你只需要把方法原型作为形参定义好 43 //感觉完全一锅写 有点类似于脚本语言的写法 44 //上面是lambda写法 下面是 匿名委托写法 45 callDel((num1, num2) => { 46 return num1 + num2; 47 }); 48 callDel((num1, num2) => { 49 return num1 - num2; 50 }); 51 52 callDel(delegate (int num1, int num2) { 53 return num1 * num2; 54 }); 55 56 MyDele d2 = delegate () 57 { 58 Console.WriteLine("call myDel2...."); 59 return 0; 60 }; 61 d2(); 62 63 MyDele d3 = new MyDele( 64 delegate { 65 Console.WriteLine("call myDel3...."); 66 return 0; 67 }); 68 d3(); 69 70 return;//----------------------------------------------------------------------------------------------- 71 //异步回调 72 MyDele d1 = new MyDele(() => { 73 //MessageBox.Show("mydel process"); 74 Console.WriteLine("mydel start"); 75 Thread.Sleep(2000); 76 Console.WriteLine("mydel end"); 77 return new Random().Next(); 78 }); 79 //d1.Invoke(); 80 81 IAsyncResult iars= d1.BeginInvoke((iar) => 82 { 83 //委托内容执行完成 后的回调 84 85 //iar 可以理解为特定的线程的句柄 进行回溯的 找到我们当初调用的 那个方法 86 //从而获取到 方法执行的结果 ,endinvolve 代表实质性的异步调用结束 可获得实实在在的执行结果 87 //既然执行到这里了 那么自然代表可以正常endinvoke了。 88 int rdmResult = d1.EndInvoke(iar); 89 Console.WriteLine("iar call back state :" + iar.AsyncState.ToString()); 90 Console.WriteLine("iar call back rst is :{0}",rdmResult); 91 }, "ss"); 92 93 //执行到这里 你无法确定能否 IsCompleted 啊 ,如果强行endinvoke 不就市区异步的意义了么, 94 //d1.EndInvoke(iars); 95 96 //比如这里我们等待3秒钟 没有执行完成 就结束线程 97 98 //wait one 就是阻塞主线程 把时间片完全让给 异步委托去执行 ,等待异步委托执行完成 。也是一种失去异步的意义的一种方式 99 //waitone 的时间 并不代表一定需要执行那么长时间,如果不带时间参数表示等待直至异步线程完成 100 iars.AsyncWaitHandle.WaitOne(); 101 Console.WriteLine("wait one 1000"); 102 //Thread.Sleep(3000); 103 //if (iars.IsCompleted) 104 // Console.WriteLine("mydel is complete"); 105 //else 106 //{ 107 // Console.WriteLine("mydel is not complete"); 108 //} 109 return;//--------------------------------------------------------------------------------------------------- 110 //空参数 的lambda 委托写法 去掉action 一样的效果 111 Thread t1 = new Thread(new ThreadStart( new Action(()=> 112 { 113 MessageBox.Show("t1 process"); 114 }))); 115 t1.Start(); 116 return;//----------------------------------------------------------------------------------------------------- 117 //用lambda的方式来写事件绑定 118 button2.Click += new EventHandler((sender2, e2) => 119 { 120 MessageBox.Show("hellow"); 121 }); 122 123 //Func 是一个带返回值形式 委托 ,就是微软新搞的一种花样 就是委托 124 //也可用lambda 的方式来写 125 List<string> strArr = new List<string>() { 126 "xiang", 127 "zhang", 128 "chen" }; 129 //var sel = strArr.Where(wheretest); 130 var sel = strArr.Where((r) => 131 { 132 if (r.Contains("g")) 133 return true; 134 else 135 return false; 136 }); 137 138 //lamda 方式的action 139 sel.ToList().ForEach(i => 140 { 141 Console.WriteLine(i); 142 }); 143 144 return;//----------------------------------------------------------------------------------------------------- 145 List<int> arr = new List<int>(); 146 arr.Add(23); 147 arr.Add(45); 148 arr.Add(22); 149 arr.ForEach((i) => 150 { 151 Console.WriteLine(i + ""); 152 }); 153 }
每次看都有不一样的东西,看山不是山 ,人生就是不断的学习。以前以为异步调用就是简单的分一个通道来做事情 ,还真不是那么简单
1 mydel d; 2 private void button3_Click(object sender, EventArgs e) 3 { 4 5 d = new mydel(deldo); 6 Console.WriteLine("aaa"); 7 //注意下面的d.BeginInvoke(null,null);并不一定要传入asyncCallBack ,根据你情况定 , 8 //比如你不需要得到异步调用的结果 (那还不如task.run() ? thread.start()? 9 //注意 注意 注意 再次说明 这个asynCallBack 在异步方法调用完成后 才会执行 ,仅仅是一个获取结果用 并无特别意思 10 11 //IAsyncResult ar= d.BeginInvoke(null,null); 12 //deldoend这个方法只有结束时才会调 ,只是相当于结束时候的一个通知 并没有其他意思 13 //如果不这样写 你就无法确定调用何时结束,当然你也可以 在begininvoke 后 同步做一些其他事, 14 //在最终的时候使用ar.synchandle.waitOne 等待调用结束 ,由此来做到不同线程的同步 15 IAsyncResult ar = d.BeginInvoke(deldoEnd, "hhh"); 16 //hhh为你想自定义传入end方法里的参数 end方法里使用ar.asyncState获取 17 Console.WriteLine("bbb"); 18 19 //Thread.Sleep(4000); 20 //Console.WriteLine("rstttt"+d.EndInvoke(ar)); 21 22 } 23 24 public int deldo() 25 { 26 Console.WriteLine("ccc"); 27 Thread.Sleep(3000); 28 Console.WriteLine(555); 29 return 5; 30 } 31 32 public void deldoEnd(IAsyncResult ar) 33 { 34 Console.WriteLine("ddd"); 35 int rest = d.EndInvoke(ar); 36 Console.WriteLine(ar.AsyncState.ToString()); 37 Console.WriteLine(rest); 38 } 39 40 delegate int mydel();
又是一些新的Task特性的使用方式,还是线程只不过换了种方式:
https://www.cnblogs.com/lonelyxmas/p/9509298.html
https://www.cnblogs.com/jesse2013/p/async-and-await.html
https://www.cnblogs.com/CreateMyself/p/5983208.html
一个简易的生产者消费者例子
今天闲得无聊 ,测试一下多线程的例子。
平常我们最简单的 list 链表 ,有个removeAt方法 最容易出错的地方是 倒序remove 其实每当remove一个后 count就自动减一了 。
为了保证索引不超出有时候还不如每次removeAt(0)。
接地系统到处用那种无脑的while true sleep方式 来刷新数据 展现 ,确实这种也是人能想到的最简单粗暴的方式 我们就不讨论那些什么事件通知 信号量玩意儿了。我们这里是一个例子。
1 //共用数据存储位置 2 List<int> ar = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8,9,10,11 }; 3 4 5 //各处生产数据的方法 每次lock占用 固定加10个数据 6 //lock的对象在各操作方法多线程间只能被唯一访问 7 public void method1(){ 8 lock (this) 9 { 10 Random rdm = new Random(); 11 for (int i = 0; i < 10; i++) 12 { 13 Thread.Sleep(10); 14 int beadd = rdm.Next(1, 100); 15 ar.Add(beadd); 16 Console.WriteLine("加入" + beadd); 17 } 18 } 19 } 20 21 //消费数据的方法 22 //无脑式的 一直运行 ,采用while 然后sleep一秒的方式 23 //当然也可以while 不加sleep 不像单片机那样把资源占完 ,只是资源占用多总是不好的也没必要那么高密度检测 24 public void method2() 25 { 26 while (true)//为什么不lock此处 想也想得到啊 一直独占消费 你到底放不放权出去让别人生产数据啊 27 { 28 lock (this)//这个lock位置必须放在此处 ,一次占用直到退出消费10个数据(期间生产数据必须等待他完成 为了避免混乱) 29 { 30 if (ar.Count > 10) 31 { 32 for (int i = 0; i < 10; i++) 33 { 34 Thread.Sleep(10); 35 Console.WriteLine("移除" + ar[ar.Count - 1]); 36 ar.RemoveAt(ar.Count - 1); 37 } 38 } 39 } 40 Thread.Sleep(1000); 41 } 42 }
1 //消费数据一直在后台运行 一有数据就会消费 2 Thread t2 = new Thread(method2); 3 t2.IsBackground = true; 4 //多线程的精妙之处 调用后会进入代码 去获权 或者称之为信号量 就是lock的那个东西 5 //获到权后 代码会自动往下走 总会执行 ,这一切是自动的 6 method1();
仿c#自带Beginxxx 异步结构 和 AutoResetEvent信号量使用
前段时间看一个原来网上下载的sock包装代码,看到里面把IAsyncResult包装了玩一种很花哨的套路,然后写成仿c#自带的beginxxx() endxxx()的结构 ,为了研究明白 今天我们也来写这么一个类似结构。
1 public class Class1 2 { 3 //异步测试入口 4 public void AsyncTest() 5 { 6 //AutoResetEvent aa = new AutoResetEvent(true); 7 ////跟嵌入式一样 set就是信号量置1,reset就是信号量置0 8 //aa.Set(); 9 ////等待一个信号量在没等到时会一直阻塞于此 10 ////while(true){} 也是类似作用 但是是一种很低级的线程间同步的手段 11 ////因为while(true){} 会把时间片占完 WaitOne() 不会 12 //aa.WaitOne(); 13 14 ////beginxxx endxxx 本质还是伪多线程 ,只不过没有Thread.Start 这几个字写在你眼前而已 15 16 byte[] data = new byte[20]; 17 IAsyncResult iar= Beginxx(data, CallBack, null); 18 ////endxxx 相当于阻塞模式运行 阻断主线程 19 ////你看 是不是就类似于 c#原生的那种套路方式了? 20 Endxxxx(iar); 21 ////也可以不Endxxx 立即下面代码获取AsyncState结果 但是状态是未完成 22 ////只能等begin代码自由运行结束 然后去触发Endxxx回调, 23 ////当你要手动Endxxx 那个代码点相当于意思是要进行同步了 手动归拢不同异步到一个时间点 24 //Console.WriteLine(iar.AsyncState.ToString()); 25 Console.WriteLine("begin后立即调用时结果:"+Encoding.UTF8.GetString(data)); 26 Thread.Sleep(4000); 27 Console.WriteLine("现在是" + Encoding.UTF8.GetString(data)); 28 //Console.WriteLine("现在的值是" + iar.AsyncState.ToString()); 29 30 31 } 32 //异步方法begin 33 //本质上传进去的 AsyncCallback这种类型的玩意儿 就只是一个method(IAsyncResult iar)这种带IAsyncResult参数的方法而已 34 //IAsyncResult 参数是为了结束时 把执行结果传给回调函数 ,并没特别意思没那么神秘 35 public IAsyncResult Beginxx(byte[] dataBeFull, AsyncCallback calback,object state) 36 { 37 //初始化一个自定义AsyncResult 完成为false 38 MyAsyncResult sresult = new MyAsyncResult(); 39 sresult.IsCompleted = false; 40 sresult.AsyncState = "aaa"; 41 42 //用Thread仿异步 执行 ,在执行完后设置IsCompleated为true 附上结果数据 43 //并调用回调委托 传result进去 44 Thread t = new Thread(() => 45 { 46 Thread.Sleep(3000); 47 48 sresult.IsCompleted = true; 49 sresult.AsyncState = "bbb"; 50 byte[] bts = Encoding.UTF8.GetBytes("bbb"); 51 52 Array.Copy(bts, dataBeFull, bts.Length); 53 calback(sresult); 54 }); 55 t.IsBackground = true; 56 t.Start(); 57 58 ////不用Thread 像下面这种代码 就没意思了哈 就不叫仿异步了 59 ////跟流水线没什么区别 60 //Thread.Sleep(2000); 61 //sresult.IsCompleted = true; 62 //sresult.AsyncState = "86547"; 63 //Console.WriteLine("ccc"); 64 //calback(sresult); 65 66 return sresult; 67 } 68 69 //这里Endxxx 相当于是封装成 c#自带的Endxxx的样子 70 public int Endxxxx(IAsyncResult iar) 71 { 72 while (iar.IsCompleted == false) 73 { 74 //如果这样一直while 不加sleep 会把时间片占完 并不友好 75 //像waitOne 这种类信号量的东西 就好的多 会把时间片让出来 76 //这里只是为了演示同步异步的原理 77 Thread.Sleep(500); 78 } 79 80 return 4684; 81 } 82 83 //这里的CallBack 相当于 是用户自己写的代码 84 public void CallBack(IAsyncResult iar){ 85 86 int rsInt = Endxxxx(iar); 87 88 Console.WriteLine("endxxx 的返回结果:"+rsInt); 89 90 string rs = iar.AsyncState.ToString(); 91 92 Console.WriteLine(rs); 93 } 94 } 95 96 public class MyAsyncResult : IAsyncResult 97 { 98 99 public object AsyncState 100 { get; set; } 101 102 public WaitHandle AsyncWaitHandle 103 { get; set; } 104 105 public bool CompletedSynchronously 106 { get; set; } 107 108 public bool IsCompleted 109 { get; set; } 110 }
异步与界面更新的新方法
一直以为更新winform界面只能用Control.BeginInvoke ,今天无意中又发现一些新方式,总体来说是利用c#新特性的语法糖,但是依然有些坑,花样是新了 但是lamda表达式转换为匿名委托的过程中一些概念的理解很让人混淆。以下代码还是用于简易socket通信结构的 onReceive事件界面更新。
1 SynchronizationContext synccontext = null; 2 3 public Form1() 4 { 5 InitializeComponent(); 6 synccontext = SynchronizationContext.Current; 7 } 8 9 10 private void Form1_Load(object sender, EventArgs e) 11 { 12 13 14 server.onReceive += new Action<Telegram_Base>((msg) => 15 { 16 //synccontext.Post(UpdateTxt, msg); 17 this.BeginInvoke( 18 new Action(() => 19 { 20 UpdateTxt(msg.p); 21 }) 22 ////总而言之 Invode 或者 BeginInvoke 需要一个确切类型的委托 光()=>{}只能代表一个匿名方法 系统不知道如何转换 23 ////Action就代表确切类型 ,但是 外面的(msg)=>{}不就代表 委托么为啥可以 真是费解 ,文章上说我们参数是具体的委托类型 就可以用匿名表达式 24 25 ////这种也是可以 意思是新线程会自动去判断 界面UI资源使用冲突咯 26 ////等同于new thread().start() 27 //(ThreadStart)(() => { 28 // UpdateTxt(msg); 29 //}) 30 31 ////文章上说 32 ////那么编译器在转化这个匿名函数的时候,就不知道要将这个匿名函数转为 ThreadStart还是MethodInvoker,于是报错 33 ////下面的代码也是可以的 34 //new MethodInvoker( 35 //delegate 36 //{ 37 // UpdateTxt(msg); 38 //}) 39 40 ////下面这种lamda 匿名委托的方式都不行的哈 ,只有action func 这种 是派生自Delegate的 metholdInvoker 41 ////()=>{} 42 ////delegate() { } 43 ); 44 }); 45 46 } 47 48 //最后,附上几个Delegate的原型: 49 //public delegate void ThreadStart(); 50 //public delegate void MethodInvoker(); 51 //public delegate void WaitCallback(object state); 52 //public delegate void ParameterizedThreadStart(object obj); 53 //public delegate void AsyncCallback(IAsyncResult ar);

浙公网安备 33010602011771号