2012-03-26 63 views
6

我的應用程序運行CPU重型algorythms來編輯放置在WPF窗口的圖像。我需要在後臺線程中完成版本。但是,嘗試在非UI線程中編輯WritableBitmap的BackBuffer將引發InvalidOperationException。如何在非UI線程中編輯WritableBitmap.BackBuffer?

private WriteableBitmap writeableBitmap; 

    private void button1_Click(object sender, RoutedEventArgs e) 
    { 
     // Create WritableBitmap in UI thread. 
     this.writeableBitmap = new WriteableBitmap(10, 10, 96, 96, PixelFormats.Bgr24, null); 
     this.image1.Source = this.writeableBitmap; 

     // Run code in non UI thread. 
     new Thread(
      () => 
      { 
       // 'Edit' bitmap in non UI thread. 
       this.writeableBitmap.Lock(); // Exception: The calling thread cannot access this object because a different thread owns it. 

       // ... At this place the CPU is highly loaded, we edit this.writeableBitmap.BackBuffer. 

       this.writeableBitmap.Unlock(); 
      }).Start(); 
    } 

我看了幾十手冊,所有的人都告訴我做的UI線程後備緩衝版(即MSDN)。

如何在非UI線程中編輯WritableBitmap.BackBuffer,而不進行任何無用的緩衝區複製/克隆?

回答

11

MSDN suggests在後臺線程中寫入後臺緩衝區。只需要在UI線程上執行某些更新前和更新後的操作。因此,儘管在後臺線程進行實際更新,UI線程可以自由地做其它事情:

 //Put this code in a method that is called from the background thread 
     long pBackBuffer = 0, backBufferStride = 0; 
     Application.Current.Dispatcher.Invoke(() => 
     {//lock bitmap in ui thread 
      _bitmap.Lock(); 
      pBackBuffer = (long)_bitmap.BackBuffer;//Make pointer available to background thread 
      backBufferStride = Bitmap.BackBufferStride; 
     }); 
     //Back to the worker thread 
     unsafe 
     { 
      //Carry out updates to the backbuffer here 
      foreach (var update in updates) 
      { 
       long bufferWithOffset = pBackBuffer + GetBufferOffset(update.X, update.Y, backBufferStride); 
       *((int*)bufferWithOffset) = update.Color; 
      } 
     } 
     Application.Current.Dispatcher.Invoke(() => 
     {//UI thread does post update operations 
      _bitmap.AddDirtyRect(new System.Windows.Int32Rect(0, 0, width, height)); 
      _bitmap.Unlock(); 
     }); 
+0

我不認爲這是正確的做法。這兩個Invoke調用將阻塞的時間比寫入緩衝區所需的時間要長。 – Brannon 2014-02-13 21:28:17

+0

我完全同意像素緩衝區的更新是快速過程的場景。如果緩衝區更新涉及一些昂貴的計算(即比兩個調用調用花費更多),那麼上述應該仍然是一個完全可行的方法。 – LOAS 2014-02-17 14:53:21

0

在WPF中,使用Dispatcher類完成跨線程調用。

在您的情況下,在無UI線程中,您需要獲取創建WritableBitmap的線程的分派器的實例。

在該調度程序然後調用調用(或者BeginInvoke的,如果你想它asynchron)

調用然後調用其中後備緩衝區是編輯

+0

但是在什麼線程中委託函數將被執行? UI線程? ThreadPool的一些隨機線程?或者是什麼? – 2012-03-26 09:26:45

2

你根本無法寫入後備緩衝來自非UI委託功能線。

除了什麼Klaus78所述,我建議如下方法:

  1. 在單獨的緩衝液(例如byte[])在ThreadPool螺紋通過的QueueUserWorkItem手段執行異步「位圖編輯」的代碼。每次需要執行異步操作時,請勿創建新的線程。這就是ThreadPool的原理。

  2. 在WriteableBitmap的Dispatcher中將編輯的緩衝區複製WritePixels。不需要鎖定/解鎖。

實施例:

private byte[] buffer = new buffer[...]; 

private void UpdateBuffer() 
{ 
    ThreadPool.QueueUserWorkItem(
     o => 
     { 
      // write data to buffer... 
      Dispatcher.BeginInvoke((Action)(() => writeableBitmap.WritePixels(..., buffer, ...))); 
     }); 
} 
+0

我已經在我的問題中發佈了'new Thread()'來隱式聲明該操作應該在其他(隨機)線程中完成。 – 2012-03-26 09:23:14

+0

同樣在我的問題中,我說我不想創建任何緩衝區,但我忘了提及我的位圖是huuuuge。這就是爲什麼你的方法有點不符合我的需要。 :) – 2012-03-26 09:24:48

+0

是的,但我害怕,否則你的問題的答案只是:你不能。 – Clemens 2012-03-26 09:29:06

3
作爲

克萊門說,這是不可能的。

你有三種選擇:

1)完成時,克萊門斯作爲建議做你的編輯在緩衝區和塊傳送。

2)在非常小的塊中進行編輯,並在GUI線程上安排好它們的優先級。如果您的工作塊保持足夠小,GUI將保持響應,但顯然這會使編輯代碼複雜化。

3)組合1 & 2.在另一個線程中編輯小塊,然後在每個塊完成時blit。這使得GUI無需使用內存即可完成後臺緩衝。

+0

是的。第二種方法很難實現,而第一種方法可以使用http://writeablebitmapex.codeplex.com/輕鬆完成。有一個快速而漂亮的Blit功能。第三種方法當然是最好的,但是我沒有時間來增強我們的應用程序。 – 2012-03-26 09:39:43

+0

建議#1沒有解決,因爲Dispatcher.BeginInvoke(... writeableBitmap.WritePixels(...,buffer,...));'花費了太多時間。結果,應用程序出現了很多問題。而且,在UI線程中執行CPU負載較重的東西看起來更加敏感。現在我將嘗試使用兩個常規位圖重新實現WritableBitmap。 – 2012-03-26 16:19:08

0

我實現了基於this answer以下,:

在視圖模型,有一個屬性像這樣,被綁定到圖像源在XAML:

private WriteableBitmap cameraImage; 
private IntPtr cameraBitmapPtr; 
public WriteableBitmap CameraImage 
{ 
    get { return cameraImage; } 
    set 
    { 
     cameraImage = value; 
     cameraBitmapPtr = cameraImage.BackBuffer; 
     NotifyPropertyChanged(); 
    } 
} 

使用屬性意味着如果WritableBitmap改變,例如由於分辨率,它將在視圖中更新,並且還將構建一個新的IntPtr。

CameraImage = new WriteableBitmap(2448, 2048, 0, 0, PixelFormats.Bgr24, null); 

在更新線程,一個新的圖像被複制在例如:

適當時,圖像構造使用:

[DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory")] 
public static extern void CopyMemory(IntPtr Destination, IntPtr Source, uint Length); 

你會做

CopyMemory(cameraImagePtr, newImagePtr, 2448 * 2048 * 3); 

有可能是這更好的功能......

在同一個線程,複製後:

parent.Dispatcher.Invoke(new Action(() => 
{ 
    cameraImage.Lock(); 
    cameraImage.AddDirtyRect(new Int32Rect(0, 0, cameraImage.PixelWidth, cameraImage.PixelHeight)); 
    cameraImage.Unlock(); 
}), DispatcherPriority.Render); 

其中parent是帶有圖像的控件/窗口。