当前位置: 代码迷 >> 综合 >> 使用Invoke,BeginInvoke 在多线程中更新UI主线程的元素
  详细解决方案

使用Invoke,BeginInvoke 在多线程中更新UI主线程的元素

热度:3   发布时间:2024-01-17 13:36:56.0

在多线程编程中,我们经常要在工作线程中去更新界面显示,而在多线程中直接调用界面控件不被允许(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!");
}
}
}

  相关解决方案