winform应用程序:非控件创建线程无法操作控件
在winform中常常会有耗时的操作,需要有进度条来与用户进行交互,告诉用户当前操作的进度状况。
在这种情况下,为避免耗时操作阻塞UI主线程,会为操作单独开一个工作线程。但是当工作线程去更新Form上的ProgressBar的进度时,异常抛出了:线程间操作无效: 从不是创建控件“progressBarUpdate”的线程访问它。
这是因为progressBarUpdate这个控件是由UI主线程创建并维护的,而工作线程直接去操作UI线程创建的控件显然是不被允许的,但是工作线程确实需要通知progressBarUpdate去更新状态,如何解决这个问题呢?
我们可以使用Control 中的方法 public IAsyncResult BeginInvoke(Delegate method, params object[] args) 来优雅的解决。
public partial class frmProduct : Form { public frmProduct() { InitializeComponent(); } public delegate void WorkDelegate(); private void button1_Click(object sender, EventArgs e) { var workThread = new Thread(new ThreadStart(DoSomeLongTermWork)); workThread.Start(); } private void DoSomeLongTermWork() { for (int i = 0; i <= 100; i++) { Thread.Sleep(100); UpdateProgressStatus(i); } } public delegate void UpdateProgressDelegate(int progress); public void UpdateProgressStatus(int progress) { if(this.InvokeRequired) { this.BeginInvoke(new UpdateProgressDelegate(UpdateProgressStatus),progress); return; } progressBarUpdate.Value = progress; } private void button2_Click(object sender, EventArgs e) { MessageBox.Show("the main ui thread is blocked!"); } }
其中 this.InvokeRequired 是Control的一个属性,它会去比较当前线程是否是winform主线程,是返回false,不需要invoke,可以直接操作control,不是则需要调用this.BeginInvoke(Delegate,parms object[] args)将更新control的操作转到winform主线程中进行。
[SRDescription("ControlInvokeRequiredDescr")] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Advanced)] public bool InvokeRequired { get { using (new Control.MultithreadSafeCallScope()) { HandleRef hWnd; if (this.IsHandleCreated) { hWnd = new HandleRef((object) this, this.Handle); } else { Control marshalingControl = this.FindMarshalingControl(); if (!marshalingControl.IsHandleCreated) return false; hWnd = new HandleRef((object) marshalingControl, marshalingControl.Handle); } int lpdwProcessId; return System.Windows.Forms.SafeNativeMethods.GetWindowThreadProcessId(hWnd, out lpdwProcessId) != System.Windows.Forms.SafeNativeMethods.GetCurrentThreadId(); } } }