TImage 在多執行緒下使用是否有 bug ? |
尚未結案
|
CA
一般會員 發表:1 回覆:10 積分:22 註冊:2007-04-01 發送簡訊給我 |
大家好:
使用 BCB5, 請問 TImage 如果有兩個執行緒同時存取它, 是不是會造成畫布掛掉? 就我查網頁所知, TImage::TCanvas 等於 TImage::TPicture::TBitmap::TCanvas 是一個記憶體畫布 另外它自 TGraphicControl 繼承了一個視窗畫布 (參考到一個 TWinControl 也就是 TImage 的 Parent) 現在如果有一個取像的程式, 可以註冊一個 callback 函式, 每次取完像它就會呼叫 callback 然後在 callback 內對 Image1 畫上擷取到的影像 (使用 API BitBlt 之類的函式直接對 Image1->Canvas->Handle 操作) 然後再使用 TCanvas::LineTo 等函式在上面一些簡單的線條, 例如一個十字線. 在畫圖前會先 Lock 畫完再 Unlock 像這樣: Image1->Canvas->Lock(); // ... paint image // ... paint cross hair Image1->Canvas->Unlock(); 我目前知道的是 TCanvas 畫圖函式在繪畫後會引發 OnChange 事件, 所以在畫完之後 (如果 Parent 是 TPanel) 會對 Panel1 發送出一個 WM_PAINT 訊息, 待主執行序有空時, 會處理這個訊息, 將 Image1->Canvas 的內容搬到 Panel1 上 在搬移之前, 會先對 Panel1 的畫布 Lock (由 TGraphicControl.WMPaint 執行) 但是好像沒有對 Image1 的畫布 Lock 我現在因為發現畫面更新比較慢, 所以在劃圖後 Unlock 前加上 Refresh 呼叫, 畫面就可以更新比較快 但是如果在 Unlock 後才 Refresh 畫布幾乎會立刻掛掉, 但如果我適當的加上 Sleep 就比較不會掛, 我不了解為什麼有這樣的差別? 因為如果主執行緒沒有對 Image1->Canvas 做 Lock 那它在搬移資料的時候, 如果剛好 callback 對 Image1->Canvas 寫入資料 應該就很有可能造成畫布掛掉, 不論 Refresh 在 Unlock 之前或之後, 掛掉機會應該是差不多一樣的, 甚至不呼叫 Refresh 也應該會有機會掛掉 但現在的結果是不呼叫 Refresh 沒遇過掛掉情形(印象中), 在 Unlock 之前 Refresh 幾乎不太會掛 (因為印象中好像發生過又好像沒有) 註: 我有查看過 TGraphicControl.WMPaint 它只對 TGraphicControl.Canvas.Lock 而 TImage.Paint 只有在呼叫 StretchDraw 前設定 FDrawing 的 Flag 而已, 並沒有對 TImage.Canvas.Lock 不知道是否看漏了 網路上找好久都找不到, 望各位大大解惑 |
daldal
高階會員 發表:6 回覆:102 積分:226 註冊:2007-06-18 發送簡訊給我 |
就裝置的角度來看
螢幕更新的頻率在 60Hz 附近 換算每次更新的間隔大概在 16.6 ms 附近 一般會大於Canvas花費的時間 (如果不是更新非常非常多Pixel) 至於Canvas底層 Refresh 使用的是同步的 api Invalidate 使用的是異步的 api 若是要讓 Canvas thread-safe 最好使用 invalidate 避免 Refresh 同步鎖定更新時 另一個執行緒剛好也發生同步互鎖的一些問題 PS: refresh 不是完全不能用在 thread, 只是要自己處理好一些同步問題 若是想要系統幫你處理,那就用異步的 invalidate 吧~ |
CA
一般會員 發表:1 回覆:10 積分:22 註冊:2007-04-01 發送簡訊給我 |
因為用 Invalidate 送 WM_PAINT 給主執行緒更新, 不是立即的
它的更新會等到主執行緒有空才做, 比較慢, 所以才想說用 Repaint 比較快 如下測試碼 [code cpp] //--------------------------------------------------------------------------- #include #pragma hdrstop volatile BOOL g_fExit = FALSE; #include "Unit1.h" //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action) { g_fExit = TRUE; } //--------------------------------------------------------------------------- DWORD WINAPI ThreadProc(LPVOID) { TCanvas* pCanvas = Form1->Image1->Canvas; int x = 5; int y = 5; while (!g_fExit) { pCanvas->Lock(); pCanvas->FillRect(Rect(0, 0, Form1->Image1->Width, Form1->Image1->Height)); pCanvas->TextOut(x, y, "Hello"); //Form1->Image1->Repaint(); //Sleep(50); x = 20; if (x > 105) x = 5; y = 20; if (y > 105) y = 5; pCanvas->Unlock(); } return 0; } //--------------------------------------------------------------------------- void __fastcall TForm1::btnThreadClick(TObject *Sender) { HANDLE hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL); CloseHandle(hThread); //btnHalt->Click(); } //--------------------------------------------------------------------------- void __fastcall TForm1::btnHaltClick(TObject *Sender) { HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); WaitForSingleObject(hEvent, 10000); CloseHandle(hEvent); } //--------------------------------------------------------------------------- [/code] TextOut 會引發 TBitmapCanvas.OnChange 最後跑到 TImage.PictureChanged 送出 Invalidate (非同步) 給 Form1 所以主執行緒一有空就會來搬資料, 把記憶體內容搬到 Form1 上 所以我 發現如果在 Unlock 之後 Repaint 會出問題, 本來不知道為什麼 後來查詢後是說 TWinControl.MainWndProc 在處理訊息後會清理畫布 呼叫這兩個函式 FreeDeviceContexts 及 FreeMemoryContexts 變成如果不把 Repaint 寫在 Lock/Unlock 之內, 可能會有競爭情況 最後在 TCanvas.RequiredState 跳出 "Canvas does not allow drawing" (因為 CreateHandle 跟 if FHandle = 0 then 之間有漏洞會出問題) 而把 Repaint 寫在 Unlock 之內, 印象中以前畫布掛掉過 不過我這兩天測試一次都沒有發生, 所以可能寫在 Lock/Unlock 就安全了吧 (搞不好是以前記錯) 但我覺得奇怪的是, 照理說應該是不安全的, 主執行緒因為 PictureChanged 收到 Invalidate 等它有空時, 去處理 WM_PAINT 時, 沒有鎖定 TImage.Canvas 只有產生的執行緒去鎖定它而已 應該有機會 FillRect 或 TextOut 到一半, 主執行緒去搬資料, 造成問題才對 (不論有沒有 Repaint 皆同) MSDN 有寫 Note that the handle to the DC can only be used by a single thread at any one time. 不是很瞭解它的意思, 怎麼測都沒問題 .. |
daldal
高階會員 發表:6 回覆:102 積分:226 註冊:2007-06-18 發送簡訊給我 |
===================引 用 CA 文 章=================== 因為用 Invalidate 送 WM_PAINT 給主執行緒更新, 不是立即的 它的更新會等到主執行緒有空才做, 比較慢, 所以才想說用 Repaint 比較快 >> draw functions, repaint 要寫在 Lock / unlock 內,不然單一 thread 還 ok ,多 thread 會隨機死 >> 之前遇到畫布會掛掉,也許是有自己去取 getdc 放到新的TCanvas, >> 不然正常下使用應該不會遇到問題 (我是用 5 thread 更新同一個 PaintBox) 但我覺得奇怪的是, 照理說應該是不安全的, 主執行緒因為 PictureChanged 收到 Invalidate 等它有空時, 去處理 WM_PAINT 時, 沒有鎖定 TImage.Canvas 只有產生的執行緒去鎖定它而已 應該有機會 FillRect 或 TextOut 到一半, 主執行緒去搬資料, 造成問題才對 (不論有沒有 Repaint 皆同) MSDN 有寫 Note that the handle to the DC can only be used by a single thread at any one time. 不是很瞭解它的意思, 怎麼測都沒問題 .. >> 而MSDN 指的是還沒封裝過的 DC 畫布, 所以沒有 thread-safe >> 兩者應該是沒有衝突的 |
CA
一般會員 發表:1 回覆:10 積分:22 註冊:2007-04-01 發送簡訊給我 |
我的問題就是在這裡, 因為 TPaintBox 跟 TImage 不同
對 TImage.Canvas 畫圖會導致最後呼叫到 TImage.PictureChanged (我在網路上查到, 把 Packages/Build with runtime packages 取消勾選, Linker/Use debug libraries 勾選, 就可以追入 VCL 源碼) 導致它呼叫 Invalidate 送出 WM_PAINT 給主執行緒, 主執行緒會不定時來搬資料, 從 TWinControl.MainWndProc -> TWinControl.WMPaint -> TWinControl.PaintHandler -> TWinControl.PaintControls -> TGraphicControl.WMPaint -> TImage.Paint 一路追下來 只有在 TGraphicControl.WMPaint 的時候發現它對 TGraphicControl.Canvas 鎖定, 似乎 沒有對 TImage.Canvas 鎖定, 變成它從 TImage.Canvas 搬資料的時候 如果我的執行緒 同時對 TImage 做 FillRect/TextOut 應該很有可能出問題才是 (不論有無 Repaint 都一樣) 但是我測了好幾天, 確實沒有問題, 可能真的是我記錯了, 不然就是某些地方沒看清楚 ^^ (不過上面程式碼如果有 Repaint 的話, 在 Win7 切換佈景主題的時候會不明原因死結 = =) 至於 GetDC 應該是沒有, 我是在改別人程式的時候測的, 它沒呼叫那個函式然後再放到 Canvas 可能有其它地方沒注意到, 當時重點不在這裡, 沒花太多心思 TPaintBox 是不會觸發事件, 它是直接用 TGraphicControl.Canvas 所以也不會在繪圖之後 呼叫 Invalidate 送出 WM_PAINT 給主執行緒, 跟 TImage (有兩個畫布) 情況不太一樣 我後來也有持續針對 Canvas does not allow drawing 查得更詳細, 官方論壇有說到 FreeDeviceContexts 跟 FreeMemoryContexts 是 VCL 自己實作的垃圾回收機制 它會在每次主執行緒處理完一個訊息之後被呼叫, 內容是會掃瞄一個全域的 CanvasList / BitmapCanvasList 把目前沒有鎖定的畫布回收, 導致的結果就是, 如果今天在非主執行緒下使用畫布 若是沒有 Lock / Unlock 即使那個副函式只有一個執行緒去執行, 仍然會出問題 因為主執行緒發現他沒有鎖定就會去釋放它 (除非主執行緒忙碌中沒時間釋放) 所以在非主執行緒下畫圖, 對畫布 Lock 是必須的不是可選的 .. 謝謝指教囉 ^^
編輯記錄
CA 重新編輯於 2010-05-04 14:02:33, 註解 無‧
|
本站聲明 |
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。 2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。 3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇! |