在多线程编程中,我们经常要在工作线程中去更新界面显示,而在多线程中直接调用界面控件不被允许(CLR出於安全考慮,默認是不允許),使用Invoke 和 BeginInvoke可以解决这个问题。
//先看个例子:通过Task从webservice获取数据后,在页面的Datagridview显示出来.
public async Task<DataTable> GetDataAsync(string dateFrom,string dateTo,string cateGory)
{
Task <DataTable > t = Task.Run(() =>
{
DataTable dtRt = new DataTable();
//1. 准备连接,各种参数
string strURL = @"https://*************";// Webservice 的地址
//创建一个HTTP请求
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(strURL);
//Post请求方式
request.Method = "POST";
//内容类型
request.ContentType = "application/json;charset=UTF-8";
request.Headers.Add("Authorization", DeveloperId);
//2. 连接 Web service
using (var postStream = new StreamWriter(request.GetRequestStream()))
{
try
{
postStream.Write(jsonParas);
postStream.Flush();
}
catch (Exception e)
{
MessageBox.Show(e.Message);//连接服务器失败
//insert a log
sqlhelper.InsertLog("*********原因*****");
return dtRt;
}
}
//3.从 webservice 获取返回的数据
String strValue = "";//strValue为http响应所返回的字符流
HttpWebResponse response;
try
{
//获得响应流
response = (HttpWebResponse)request.GetResponse();
}
catch (WebException ex)
{
response = ex.Response as HttpWebResponse;
}
Stream s = response.GetResponseStream();
StreamReader myStreamReader = new StreamReader(s, Encoding.GetEncoding("utf-8"));
strValue = myStreamReader.ReadToEnd();
myStreamReader.Close();
s.Close();
dtRt = JsonHelper.jsonToDataTable(strValue);// 自定义函数:將json轉為datatable
//***************使用 BeginInvoke****************
dgv1.BeginInvoke(new Action(()=> {
dgv1.DataSource = dtRt;
}));
// 或: dgv1.BeginInvoke((MethodInvoker)delegate
// {
// dgv1.DataSource = dtRt;
// });
return dtRt;
});
return await t;
}
另一個進度條的例子:
private void button12_Click(object sender, EventArgs e)
{
Task t = Task.Run(() =>
{
for (int i = 0; i <= 100; i++)
{
progressBar1.Invoke(new Action(() => {
progressBar1.Value = i;
}));
Thread.Sleep(100);
}
});
}
进一步,再了解下
Invoke 和 BeginInvoke
首先invoke和begininvoke的使用有两种情况:
第一种: control的invoke、begininvoke。
第二种:delegrate的invoke、begininvoke。
先学习第一种:control的invoke、begininvoke
1.Control.Invoke 方法 (Delegate) :在拥有此控件的基础窗口句柄的线程上执行指定的委托。
2.Control.BeginInvoke 方法 (Delegate) :在创建控件的基础句柄所在线程上异步执行指定委托。
3.Control的Invoke和BeginInvoke 是相对于分线程(一般在分线程中调用,用来更新主线程ui)Invoke立即插入主线程中执行(大致理解invoke表是同步);而BeginInvoke 表示异步,它是相对于调用线程的异步,而不是相对于UI线程的异步。两者的区别就是一个导致工作线程等待,而另外一个则不会。(這點文章最后的例子有說明)
4.Control的Invoke和BeginInvoke与Delegate的Invoke和BeginInvoke是不同的。
5.Control的Invoke和BeginInvoke的参数为delegate,delegate中的方法是在Control的线程中执行的,即UI线程中執行。(這點文章最后的例子有說明)
因为界面更新始终要通过 UI 线程,我们可以在分线程中进行大部分的逻辑运算,而将界面更新放到 UI 线程中去做,达到了减轻 UI 线程负担的目的。
再举个简单例子,比如启动一个线程,在线程的方法中更新窗体中的一个控件
//启动一个线程
Thread thread=new Thread(new ThreadStart(DoWork));
thread.Start();
//线程方法
private void DoWork()
{
this.TextBox1.Text="文本框";
}
上面操作,在VS里有异常的
可以使用Invoke\BeginInvoke
如果你的后台线程在更新一个UI控件的状态后不需要等待,而是要继续往下处理,就使用BeginInvoke异步处理。
如果你的后台线程需要操作UI控件,并且需要等到该操作执行完毕才能继续执行,就使用Invoke。
public void DoWork(object txt) --如果是thread調用,注意参数是object。task 調用的話就無謂。
{
Console.WriteLine(" Task thread id is:" + Thread.CurrentThread.ManagedThreadId.ToString()); //Task thread id is: 4 ,,,新的線程
TextBox1.BeginInvoke(new Action(()=> {
Console.WriteLine(" Invoke thread id is:" + Thread.CurrentThread.ManagedThreadId.ToString()); //Invoke thread id is:1 ,,,回到UI主線程
TextBox1.Text=Convert.Tostring(txt);
Console.WriteLine("Invoke1!");
Thread.Sleep(5000);--模擬阻塞
Console.WriteLine("Invoke2!");
}));
Console.WriteLine("Done!"); // 上面語句如果是BeginInvoke,則這個及后面語句不會被阻塞,它的輸出在“Invoke1”,“Invoke2”之前。反之,用Invoke則會,它的輸出在之後。
}
private void button1_Click(object sender, EventArgs e)
{
Console.WriteLine("Main Thread id is:" + Thread.CurrentThread.ManagedThreadId.ToString()); //Main Thread id is:1 ,,,,UI 主線程
Task task = Task.Run(() => DoWork(“Text changed!”));
//Thread thread = new Thread(DoWork);
//thread.Start("Text changed!");
}
}
}