2012-03-26 24 views
6

Ứng dụng của tôi chạy algorythms CPU nặng để chỉnh sửa hình ảnh được đặt tại cửa sổ WPF. Tôi cần phiên bản được thực hiện trong một chủ đề nền. Tuy nhiên, cố gắng chỉnh sửa BackBuffer của WritableBitmap trong chuỗi giao diện người dùng không phải là InvalidOperationException.Làm thế nào để chỉnh sửa một WritableBitmap.BackBuffer trong thread UI không?

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(); 
    } 

Tôi đã đọc hàng chục sách hướng dẫn, tất cả đều cho tôi biết ấn bản BackBuffer trong chuỗi giao diện người dùng (ví dụ: MSDN).

Cách chỉnh sửa WritableBitmap.BackBuffer trong một chuỗi giao diện người dùng không có bất kỳ sao chép/sao chép bộ đệm vô dụng nào?

Trả lời

11

MSDN suggests ghi vào bộ đệm sau trong chuỗi nền. Chỉ một số hoạt động cập nhật trước và sau nhất định cần phải được thực hiện trên luồng giao diện người dùng.Vì vậy, trong khi thread nền đang làm việc cập nhật thực tế, thread UI được tự do làm những thứ khác:

 //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

Tôi không nghĩ đây là phương pháp phù hợp. Hai cuộc gọi Invoke sẽ chặn lâu hơn thời gian cần thiết để viết bộ đệm. – Brannon

+0

Tôi hoàn toàn đồng ý với các tình huống khi cập nhật bộ đệm pixel là một quá trình nhanh chóng. Nếu cập nhật bộ đệm liên quan đến một số tính toán đắt tiền (tức là chi phí nhiều hơn hai cuộc gọi gọi) thì ở trên vẫn phải là một cách tiếp cận hoàn toàn khả thi. – LOAS

0

Trong các cuộc gọi qua luồng WPF được thực hiện bằng cách sử dụng lớp Dispatcher.

Trong trường hợp của bạn trong chuỗi không có giao diện người dùng, bạn cần lấy phiên bản của Trình điều phối của chuỗi nơi tạo WritableBitmap.

On phối mà sau đó gọi Gọi (hoặc BeginInvoke nếu bạn muốn nó asynchron)

Invoke sau đó gọi một chức năng đại diện trong đó các BackBuffer được chỉnh sửa

+0

Nhưng chức năng đại biểu sẽ được thực thi trong chuỗi nào? Chuỗi giao diện người dùng? Một số chủ đề ngẫu nhiên từ ThreadPool? Hay cái gì? –

2

Bạn chỉ đơn giản là không thể ghi vào BackBuffer từ một giao diện người dùng không chủ đề.

Ngoài những gì Klaus78 nói, tôi sẽ đề nghị các phương pháp sau đây:

  1. Thực hiện đồng bộ "chỉnh sửa bitmap" mã trên một bộ đệm riêng biệt (ví dụ byte[]) trong một thread ThreadPool bằng QueueUserWorkItem. Không tạo một Thread mới mỗi khi bạn cần thực hiện một thao tác không đồng bộ. Đó là những gì ThreadPool đã được thực hiện cho.

  2. Sao chép bộ đệm đã chỉnh sửa theo WritePixels trong Bộ điều phối của WriteableBitmap. Không cần Lock/Unlock.

Ví dụ:

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

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

Tôi đã đăng 'Thread mới()' trong câu hỏi của tôi chỉ để nhà nước implicilty rằng các hoạt động nên được thực hiện trong một số khác (ngẫu nhiên) thread. –

+0

Cũng trong câu hỏi của tôi, tôi đã nói rằng tôi không muốn tạo bất kỳ bộ đệm nào, nhưng tôi quên đề cập đến bitmap của tôi là huuuuge. Đó là lý do tại sao cách tiếp cận của bạn một chút không phải là một trong những tôi cần. :) –

+0

Có, nhưng tôi sợ rằng nếu không câu trả lời cho câu hỏi của bạn đơn giản là: bạn không thể. – Clemens

3

Như Clemens nói, điều này là không thể.

Bạn có ba lựa chọn:

1) Do chỉnh sửa của bạn trong một bộ đệm và blit khi hoàn tất như Clemens gợi ý.

2) Thực hiện chỉnh sửa trong các đoạn rất nhỏ và lên lịch cho chúng ở mức ưu tiên tốt đẹp trên luồng GUI. Nếu bạn giữ khối lượng công việc đủ nhỏ, GUI sẽ vẫn đáp ứng, nhưng rõ ràng điều này làm phức tạp mã chỉnh sửa.

3) Kết hợp 1 & 2. Chỉnh sửa các đoạn nhỏ trong chuỗi khác, sau đó ghi từng đoạn khi hoàn thành. Điều này giúp GUI đáp ứng mà không cần sử dụng bộ nhớ cho một bộ đệm đầy đủ.

+0

Vâng. Cách tiếp cận thứ hai khó thực hiện, trong khi cách đầu tiên có thể được thực hiện dễ dàng bằng cách sử dụng http://writeablebitmapex.codeplex.com/ Có một hàm Blit nhanh và đẹp. Và cách tiếp cận thứ ba là cách tốt nhất, tuy nhiên tôi không có thời gian để nâng cao ứng dụng của mình nhiều. –

+0

Đề xuất # 1 không được giải quyết vì 'Dispatcher.BeginInvoke (... writeableBitmap.WritePixels (..., buffer, ...));' mất quá nhiều thời gian. Như là kết quả applcation ổn định rất nhiều. Hơn nữa, làm công cụ nặng CPU trong giao diện người dùng giao diện người dùng trông thậm chí còn phản hồi nhanh hơn. Bây giờ tôi sẽ cố gắng reimplement WritableBitmap bằng cách sử dụng hai Bitmap thông thường. –

0

tôi thực hiện những điều sau đây, dựa trên this answer:

Trong mô hình xem, có một bất động sản như thế này, liên kết với nguồn Hình ảnh trong XAML:

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

Sử dụng thuộc tính có nghĩa là nếu WritableBitmap thay đổi, ví dụ: vì độ phân giải, nó sẽ được cập nhật trong View và cũng là một IntPtr mới sẽ được xây dựng.

Những hình ảnh được xây dựng khi thích hợp:

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

Trong thread cập nhật, một hình ảnh mới được sao chép trong, ví dụ sử dụng:

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

bạn sẽ làm gì

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

Có thể có một chức năng tốt hơn cho điều này ...

Trong cùng một sợi, sau khi sao chép:

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

trong đó parent là Điều khiển/Cửa sổ có Hình ảnh.

Các vấn đề liên quan