C#异步跨线程
来源:互联网 发布:广元外卖软件 编辑:程序博客网 时间:2024/06/08 16:15
最近学习C#的异步操作,总是搞不明白什么意思,然后今天想自己写一下整理一下思路。
在窗体编程中,经常会碰到界面假死的状态,原因是什么呢?
首先我们看一个造成假死的一个例子
我写了一个工程,目的是在点击开始按钮后progressBar.Value每隔10秒加一,然后richTextBox打印出来当前的progressBar.Value
部分代码如下
private void UpdataUI() { for (int i = 0; i < 100; i++) { Thread.Sleep(1 * 1000); //长时间请求 progressBar.Value = i; richTextBox.AppendText(i.ToString() + "\n"); richTextBox.SelectionStart = richTextBox.Text.Length; richTextBox.ScrollToCaret(); } } private void btn_Start_Click(object sender, EventArgs e) { UpdataUI(); }
此时如果运行程序,点击开始后,界面将会造成假死
其次我们来讨论一下Windows的消息机制
我们知道窗体中用户的更种操作都是基于消息传输的,在Win32中我们差不多都会见到这段代码
/* Enter the modified message loop */while (GetMessage (&msg, NULL, 0, 0)){ if (!TranslateMDISysAccel (hwndClient, &msg) && !TranslateAccelerator (hwndFrame, hAccel, &msg)) { TranslateMessage (&msg); DispatchMessage (&msg); }}
上面的意思就是说GetMessage()将消息队列中的消息依次取出,然后转换成可以理解的东西,然后分发出去执行在switch中执行自己的代码
switch (message){ case WM_CREATE: case WM_COMMAND: case WM_PAINT: . . . .......}
用户的输入就是靠消息循环来执行的,如果现在有一个很耗时的操作也在消息循环中执行,即我们通常所说的UI线程中,那么后面的操作就会得不到响应,上面的代码中等待10秒的操作就是在UI线程中执行的,所以出现了所谓的假死,碰到这种情况就需要异步操作。
现在我们来用异步的方法解决这个问题
当你定义一个委托后,编译器会自动的为你生成一个类,还会为你在这个类里提供一个BeginInvoke方法和一个EndInvoke方法,这两个方法的实现是由CLR提供的,而这个BeginInvoke和EndInvoke只是起一个包装的作用。现在我们就用这两个方法来实现异步操作
public Form1() { InitializeComponent(); checkForIllegalCrossThreadCalls = false;//先加上,后面解释 } private void UpdataUI() { for (int i = 0; i < 100; i++) { Thread.Sleep(1 * 1000); //长时间请求 progressBar.Value = i; richTextBox.AppendText(i.ToString() + "\n"); richTextBox.SelectionStart = richTextBox.Text.Length; richTextBox.ScrollToCaret(); } } private void btn_Start_Click(object sender, EventArgs e) { Action act = new Action(UpdataUI); act.BeginInvoke(null, null);//实现异步,相当于创建一个后台的工作线程来等待更新界面 }
我们发现界面不在假死,一切正常了。
上面的异步操作没有返回值,但是通常我们在异步后会等待一个返回值,我们稍微修改一下代码让其有一个返回值
public Form1() { InitializeComponent(); checkForIllegalCrossThreadCalls = false;//先加上,后面解释 } private string UpdataUI() { for (int i = 0; i < 100; i++) { Thread.Sleep(1 * 1000); //长时间请求 //ProgressBarCalc(i); richTextBox.AppendText(i.ToString() + "\n"); progressBar.Value = i; } return "Finshed"; } private void btn_Start_Click(object sender, EventArgs e) { Func<string> act = new Func<string>(UpdataUI); IAsyncResult asyncResult = act.BeginInvoke((result) => { string ret = act.EndInvoke(result); MessageBox.Show(ret); }, null);//实现异步,相当于创建一个后台的工作线程来等待更新界面 }
在这里来说明一下BeginInvoke
此方法将“异步”执行委托所指向的那个方法。所谓“异步”,就是结果并不是像调用“Invoke”方法一样直接就出现结果,BeginInvoke将在内部开辟一个新线程(注意:是后台线程!)去执行这个委托方法。因为是后台线程,因此如果主程序一旦关闭或者停止,无论后台线程的任务是否执行完毕,都将自动终止。本示例因为是WinForm,一旦运行主线程不会自动结束,但是对于诸如控制台一类的程序则不然,因此控制台程序如果使用BeginInvoke方法,必须要在其后面调用对应的EndInvoke。EndInvoke方法会阻塞当前进程,直至BeginInvoke执行完毕所有的委托方法后直接返回一个结果。
在上面的程序中我们加入了一句代码
checkForIllegalCrossThreadCalls = false;
那么这句是干什么用的呢,不妨先去掉然后运行一下,突然就报异常了,如何跨线程调用Windows窗体控件
通过调试我们发现程序主要检查checkForIllegalCrossThreadCalls、inCrossThreadSafeCall两个字段以及InvokeRequired,InvokeRequired的职责是判断当前运行的线程是不是与窗体主线程是同一个线程。
通过字面理解checkForIllegalCrossThreadCalls的意思就是“跨线程调用时是不是检查”。而这个字段在Control的静态构造函数里被设置为:checkForIllegalCrossThreadCalls = Debugger.IsAttached(checkForIllegalCrossThreadCalls 还通过CheckForIllegalCrossThreadCalls属性公开了 ),现在明白了为啥在Visual Studio里调试程序报异常,而独立运行却不报了吧。
那么该怎么解决呢,我们还是用异步的方法解决,但是现在要用Control.Invoke和Control.BeginInvoke这个是干什么的呢?这个的创建的异步线程是指相对于创建线程的线程是异步的,说的简单点就是指我们在UI线程中使用delegate.BeginInvoke的方法创建了一个相对于UI线程异步的线程,在异步线程中创建了一个相对于delegate.BeginInvoke异步的线程,即UI线程(我真心讲不清楚了,也不知道具体原因)。
private void ProgressBarCalc(int value) { if(this.InvokeRequired)//判断是否与UI线程在同一线程,如果不是就创建一个代理 { this.Invoke(new Action<int>(ProgressBarCalc),value); } else//如果是就直接更新 { richTextBox.AppendText(value.ToString() + "\n"); progressBar.Value = value; } }
再次运行后我们发现一起OK
献出所有代码
using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Windows.Forms;using System.Threading;namespace WindowsFormsApplication9{ public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void ProgressBarCalc(int value)//通过异步更新界面,跨线程调用Windows窗体控件 { if(this.InvokeRequired)//判断是否与UI线程在同一线程,如果不是就创建一个代理 { this.Invoke(new Action<int>(ProgressBarCalc),value); } else//如果是就直接更新 { richTextBox.AppendText(value.ToString() + "\n"); progressBar.Value = value; } } private string UpdataUI()//长时间的操作 { for (int i = 0; i < 100; i++) { Thread.Sleep(1 * 1000); //长时间请求 ProgressBarCalc(i); //更新界面 } return "Finshed"; } private void btn_Start_Click(object sender, EventArgs e) { Func<string> act = new Func<string>(UpdataUI); IAsyncResult asyncResult = act.BeginInvoke((result) => { string ret = act.EndInvoke(result);//执行完异步操作后得到返回结果 MessageBox.Show(ret);//显示出结果 }, null);//实现异步,相当于创建一个后台的工作线程来等待更新界面 } }}
- C#异步跨线程
- C# 线程与异步
- C#线程 异步调用
- C#异步和线程
- C# 线程,异步,协程
- C# 异步与线程
- C#异步线程(一)异步委托
- C#线程与异步调用
- c#多线程:线程池和异步编程
- c#多线程:线程池和异步编程
- c#中的线程同步或者异步问题
- C#异步调用与线程总结
- c#多线程:线程池和异步编程
- C# 异步调用与线程总结
- C#异步调用和线程的同步
- C#异步调用与线程总结
- C#线程通信和异步委托
- C#线程通信和异步委托
- shell 生成随机密码
- linq to entity 的文集集锦
- 路由跳数
- fgets读取一行数据
- C#中 一个多线程框架
- C#异步跨线程
- 1002455 - Errors in texts: Messages VX 201, VX 111, and VX 205
- Android版busybox编译
- java解惑之名字重用的术语表
- PHP采集程序中常用的函数
- 使用inline-block实现图片列表展示(对比float的实现)
- 调用函数排序
- WebService 之 WSDL文件 讲解
- 子类、父类、静态成员变量,构造函数的执行顺序