你們知道WPF中多線程訪問UI控件時會提示UI線程的數據不能直接被其餘線程訪問或者修改,該怎樣來作呢?
分下面兩種狀況算法
1.WinForm程序安全
1)第一種方法,使用委託:
private delegate void SetTextCallback(string text); private void SetText(string text) { // InvokeRequired須要比較調用線程ID和建立線程ID // 若是它們不相同則返回true if (this.txt_Name.InvokeRequired) { SetTextCallback d = new SetTextCallback(SetText); this.Invoke(d, new object[] { text }); } else { this.txt_Name.Text = text; } } 2)第二種方法,使用匿名委託 private void SetText(Object obj) { if (this.InvokeRequired) { this.Invoke(new MethodInvoker(delegate { this.txt_Name.Text = obj; })); } else { this.txt_Name.Text = obj; } } 這裏說一下BeginInvoke和Invoke和區別:BeginInvoke會當即返回,Invoke會等執行完後再返回。
2.WPF程序多線程
1)可使用Dispatcher線程模型來修改框架
若是是窗體自己可以使用相似以下的代碼:異步
this.lblState.Dispatcher.Invoke(new Action(delegate { this.lblState.Content = "狀態:" + this._statusText; }));
那麼假如是在一個公共類中彈出一個窗口、播放聲音等呢?這裏咱們可使用:System.Windows.Application.Current.Dispatcher,以下所示async
System.Windows.Application.Current.Dispatcher.Invoke(new Action(() => { if (path.EndsWith(".mp3") || path.EndsWith(".wma") || path.EndsWith(".wav")) { _player.Open(new Uri(path)); _player.Play(); } }));
EmguCV中的Capture類能夠完成視頻文件的讀取,並捕捉每一幀,能夠利用Capture類完成實現WinForm中視頻檢測跟蹤環境的搭建。本文只實現最簡陋的WinForm + EmguCV上的avi文件讀取和播放框架,複雜的檢測和跟蹤算法在以後添加進去。ide
這裏使用WinForm實現視頻的播放,主要是PictureBox類,它是支持基於事件的異步模式的典型組件,不使用EmguCV自帶的UI控件等。優化
圖1.效果圖ui
直接在UI線程中完成視頻的播放的話整個程序只有一個線程,因爲程序只能同步執行,播放視頻的時候UI將中止響應用戶的輸入,形成界面的假死。因此視頻的播放須要實現異步模式。主要有三種方法:第一是使用異步委託;第二種是使用BackgroundWorker組件;最後一種就是使用多線程(不使用CheckForIllegalCrossThreadCalls =false的危險作法)。this
Windows窗體控件,惟一能夠從建立它的線程以外的線程中調用的是Invoke()、BegionInvoke()、EndInvoke()方法和InvokeRequired屬性。其中BegionInvoke()、EndInvoke()方法是Invoke()方法的異步版本。這些方法會切換到建立控件的線程上,以調用賦予一個委託參數的方法,該委託參數能夠傳遞給這些方法。
(一) 使用多線程
首先定義監控的類及其對應的事件參數類和異常類:
判斷是否繼續執行的布爾型成員會被調用線程改變,所以聲名爲volatile,不進行優化。
/// <summary> /// 紅外檢測子。 /// </summary> public class ThermalSurveillant { #region Private Fields /// <summary> /// 是否中止線程,此變量供多個線程訪問。 /// </summary> private volatile bool shouldStop = false; #endregion #region Public Properties #endregion #region Public Events /// <summary> /// 幀刷新事件。 /// </summary> public EventHandler<FrameRefreshEventArgs> FrameRefresh; /// <summary> /// 播放完成。 /// </summary> public EventHandler<CompletedEventArgs> Completed; #endregion #region Protected Methods /// <summary> /// 處理幀刷新事件。 /// </summary> /// <param name="e"></param> protected virtual void OnFrameRefresh(FrameRefreshEventArgs e) { if (this.FrameRefresh != null) { this.FrameRefresh(this, e); } } /// <summary> /// 處理視頻讀完事件。 /// </summary> /// <param name="e"></param> protected virtual void OnCompleted(CompletedEventArgs e) { if (this.Completed != null) { this.Completed(this, e); } } #endregion #region Public Methods /// <summary> /// 視頻監控。 /// </summary> /// <param name="capture">捕捉。</param> public void DoSurveillance(Object oCapture) { Capture capture = oCapture as Capture; int id = 1; if (capture == null) { throw new InvalidCaptureObjectException("傳遞的Capture類型無效。"); } while (!shouldStop) { Image<Bgr, byte> frame = capture.QueryFrame(); if (frame != null) { FrameRefreshEventArgs e = new FrameRefreshEventArgs(frame.ToBitmap(), id++); // 觸發刷新事件 this.OnFrameRefresh(e); } else { break; } } // 觸發完成事件 this.OnCompleted(new CompletedEventArgs(id)); } /// <summary> /// 請求中止線程。 /// </summary> public void Cancel() { this.shouldStop = true; } #endregion }
UI線程中啓動播放線程:
聲明:
/// <summary> /// 監控線程。 /// </summary> private Thread threadSurveillance = null; /// <summary> /// 捕獲視頻幀。 /// </summary> private Capture captureSurveillance; /// <summary> /// 監控子。 /// </summary> private ThermalSurveillant surveillant = new ThermalSurveillant();
讀入視頻文件:
captureSurveillance = new Capture(this.videoFilePath); captureSurveillance.SetCaptureProperty(CAP_PROP.CV_CAP_PROP_FRAME_WIDTH, this.width); captureSurveillance.SetCaptureProperty(CAP_PROP.CV_CAP_PROP_FRAME_HEIGHT, this.height); Image<Bgr, byte> frame = captureSurveillance.QueryFrame(); this.pictureBox.Image = frame.ToBitmap();
播放視頻文件:
UI線程中響應監控類的事件:
定義異步調用的委託:
添加事件委託:
this.surveillant.FrameRefresh += OnRefreshFrame; this.surveillant.Completed += OnCompleted;
如下方法中都是由監控線程中的事件委託方法,應該使用BeginInvoke方法,這樣能夠優雅的結束線程,若是使用Invoke方法,則調用方式爲同步調用,此時若是使用Thread.Join()方法終止線程將引起死鎖(正常播放沒有問題),Thread.Join()方法的使用使調用線程阻塞等待當前線程完成,在這裏即UI線程阻塞等待監控線程完成,而監控線程中又觸發UI線程中pictureBox的刷新,使用Invoke方法就形成了監控線程等待UI線程刷新結果,而UI線程已經阻塞,造成了死鎖。死鎖時只能用Thread.Abort()方法才能結束線程。或者直接強制結束應用程序。
使用BeginInvoke方法時爲異步調用,監控線程不等待刷新結果直接繼續執行,能夠正常結束。結束後UI才進行刷新,不會形成死鎖。
圖2.線程關係
/// <summary> /// 刷新UI線程的pixtureBox的方法。 /// </summary> /// <param name="frame">要刷新的幀。</param> private void RefreshFrame(Bitmap frame) { this.pictureBox.Image = frame; // 這裏必定不能刷新!2012年8月2日1:50:16 //this.pictureBox.Refresh(); } /// <summary> /// 響應pictureBox刷新。 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void OnRefreshFrame(object sender, FrameRefreshEventArgs e) { // 判斷是否須要跨線程調用 if (this.pictureBox.InvokeRequired == true) { FrameRefreshDelegate fresh = this.RefreshFrame; this.BeginInvoke(fresh, e.Frame); } else { this.RefreshFrame(e.Frame); } } /// <summary> /// 響應Label刷新信息。 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void OnCompleted(object sender, CompletedEventArgs e) { // 判斷是否須要跨線程調用 CompletedDelegate fresh = this.RefreshStatus; string message = "視頻結束,共 " + e.FrameCount + " 幀。"; this.BeginInvoke(fresh, message); } 關閉時須要停止播放線程以後再退出: /// <summary> /// 關閉窗體時發生。 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void OnFormClosed(object sender, FormClosedEventArgs e) { // 檢測子算法請求終止 surveillant.Cancel(); // 阻塞調用線程直到檢測子線程終止 if (threadSurveillance != null) { if (threadSurveillance.IsAlive == true) { threadSurveillance.Join(); } } }
(二) 使用異步委託
建立線程的一個更簡單的方法是定義一個委託,並異步調用它。委託是方法的類型安全的引用。Delegate類還支持異步地調用方法。在後臺,Delegate類會建立一個執行任務的線程。
// asynchronous by using a delegate PlayVideoDelegate play = this.PlayVideoFile; IAsyncResult status = play.BeginInvoke(null, null); /// <summary> /// 播放視頻文件。 /// </summary> private void PlayVideoFile() { while (true) { Image<Bgr, byte> frame = capture.QueryFrame(); if (frame != null) { Image<Gray, byte> grayFrame = frame.Convert<Gray, byte>(); grayFrame.Resize(this.width, this.height, INTER.CV_INTER_CUBIC); RefreshPictureBoxDelegate fresh = this.RefreshPictureBox; try { this.BeginInvoke(fresh, grayFrame.ToBitmap()); } catch (ObjectDisposedException ex) { Thread.CurrentThread.Abort(); } } else { break; } } } /// <summary> /// 刷新UI線程的pixtureBox的方法。 /// </summary> /// <param name="frame">要刷新的幀。</param> private void RefreshPictureBox(Bitmap frame) { this.pictureBox.Image = frame; }
(三) 使用BackgroundWorker組件
BackgroundWorker類是異步事件的一種實現方案,異步組件能夠選擇性的支持取消操做,並提供進度信息。RunWorkerAsync()方法啓動異步調用。CancelAsync()方法取消。
圖3.BackgroundWorker組件
/// <summary>
/// 播放視頻文件。 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void detectItemPlay_Click(object sender, EventArgs e) { if (this.videoFilePath != null) { // run async this.backgroundWorker.RunWorkerAsync(capture); } } /// <summary> /// 異步調用。 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void OnDoWork(object sender, DoWorkEventArgs e) { Emgu.CV.Capture capture = e.Argument as Emgu.CV.Capture; while (!e.Cancel) { Image<Bgr, byte> frame = capture.QueryFrame(); if (frame != null) { Image<Gray, byte> grayFrame = frame.Convert<Gray, byte>(); grayFrame.Resize(this.width, this.height, INTER.CV_INTER_CUBIC); if (this.backgroundWorker.CancellationPending == true) { e.Cancel = true; break; } else { if (this.pictureBox.InvokeRequired == true) { RefreshPictureBoxDelegate fresh = this.RefreshPictureBox; this.BeginInvoke(fresh, grayFrame.ToBitmap()); } else { this.RefreshPictureBox(grayFrame.ToBitmap()); } } } else { break; } } } /// <summary> /// 關閉窗體時發生。 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void MainForm_FormClosed(object sender, FormClosedEventArgs e) { if (this.backgroundWorker.IsBusy) { this.backgroundWorker.CancelAsync(); } }轉自http://blog.csdn.net/azkabannull/article/details/7827673