WriteableBitmap上的异步操作

我正在用WPF(C#)编写一个应用程序,它对一组Bitmaps进行长时间的操作。 为了使我的应用程序保持响应,我决定使用另一个线程来对位图执行操作,并在主UI线程中报告进度条的进度。 我认为BackgroundWorker会为我做任何事情,但看起来不会那么容易。

我有以下代码:

public class ImageProcessor { public Collection Pictures { get; private set; } private BackgroundWorker _worker = new BackgroundWorker(); public ImageProcessor() { _worker.DoWork += DoWork; } public void DoLotsOfOperations() { _worker.RunWorkerAsync(); } private void DoWork(object sender, DoWorkEventArgs e) { // operations on Pictures collection } } 

在运行时,我使用标准打开文件对话框将图像加载到Pictures集合中,然后调用DoLotsOfOperations()方法。 但是当我尝试访问单个位图的任何属性时,我得到InvalidOperationException:“调用线程无法访问该对象,因为不同的线程拥有它”。

这是真的 – 我加载了位图并在UI线程中填充了集合,我尝试在另一个线程中读取集合元素。 所以我尝试了不同的方法:

  • 我将整个集合作为RunWorkerAsync方法的参数传递,然后从e.Argument中将其返回到DoWork方法中,但是当我尝试读取单个位图的属性时,我仍然得到了相同的exception。
  • 我尝试了同样的事情,这次将一个位图作为backgroundworker的参数传递,但仍然无法得到任何位图的属性,更不用说位图的像素了。

那么如何在另一个线程中访问位图的数据(最好是使用BackgroundWorker)?

我不知道,也许我的整个方法都是错的。 我想要实现的一般想法是:

  1. 用户加载位图然后显示在窗口中。
  2. 用户单击按钮并对位图执行长操作,但UI响应(例如,允许用户取消操作),并在progess栏上报告进度。

在此先感谢您的帮助。

1)代理类(没有线程限制)

  public class WriteableBitmapProxy { public IntPtr BackBuffer { get; set; } public int BackBufferStride { get; set; } public int PixelHeight { get; set; } public int PixelWidth { get; set; } } 

2)扩展方法(不安全)

  public class RGBColor { public byte R { get; set; } public byte G { get; set; } public byte B { get; set; } public uint Value { get { return (uint)(((uint)R << 16) + ((uint)G << 8) + (B) + ((uint)255 << 24)); } } } public static RGBColor GetPixel(this WriteableBitmap bmp, uint x, uint y) { unsafe { if (y >= bmp.PixelHeight) return default(RGBColor); if (x >= bmp.PixelWidth) return default(RGBColor); // Get a pointer to the back buffer. uint pBackBuffer = (uint)bmp.BackBuffer; // Find the address of the pixel to draw. pBackBuffer += y * (uint)bmp.BackBufferStride; pBackBuffer += x * 4; byte* pCol = (byte*)pBackBuffer; return new RGBColor() { B = pCol[0], G = pCol[1], R = pCol[2] }; } } public static void SetPixel(this WriteableBitmapProxy bmp, uint x, uint y, RGBColor col) { SetPixel(bmp, x, y, col.Value); } public static void SetPixel(this WriteableBitmapProxy bmp, uint x, uint y, uint value) { unsafe { if (y >= bmp.PixelHeight) return; if (x >= bmp.PixelWidth) return; // Get a pointer to the back buffer. uint pBackBuffer = (uint)bmp.BackBuffer; // Find the address of the pixel to draw. pBackBuffer += y * (uint)bmp.BackBufferStride; pBackBuffer += x * 4; // Assign the color data to the pixel. *((uint*)pBackBuffer) = value; } } 

3)在不同的线程中触发长时间运行的程序

  var image = sender as Image; var bitmap = image.Source as WriteableBitmap; var prx = new WpfImage.MyToolkit.WriteableBitmapProxy() { BackBuffer = bitmap.BackBuffer, BackBufferStride = bitmap.BackBufferStride, PixelHeight = bitmap.PixelHeight, PixelWidth = bitmap.PixelWidth }; bitmap.Lock(); Thread loader = new Thread(new ThreadStart(() => { Global_Histogramm(prx); Dispatcher.BeginInvoke(DispatcherPriority.Background, (SendOrPostCallback)delegate { bitmap.AddDirtyRect(new Int32Rect(0, 0, prx.PixelWidth - 1, prx.PixelHeight - 1)); bitmap.Unlock(); }, null); } )); loader.Priority = ThreadPriority.Lowest; loader.Start(); 

4)长时间运行操作实现

  void Global_Histogramm(WpfImage.MyToolkit.WriteableBitmapProxy src) { int SrcX = src.PixelWidth; int SrcY = src.PixelHeight; double[] HR = new double[256]; double[] HG = new double[256]; double[] HB = new double[256]; double[] DR = new double[256]; double[] DG = new double[256]; double[] DB = new double[256]; uint i, x, y; // wyzeruj tablice for (i = 0; i < 256; i++) HB[i] = HG[i] = HR[i] = 0; // wypelnij histogramy RGB for (y = 0; y < SrcY; y++) for (x = 0; x < SrcX; x++) { var color = src.GetPixel(x, y); HB[color.B]++; HG[color.G]++; HR[color.R]++; }; // oblicz histogramy znormalizowane i przygotuj dystrybuanty int ilosc_punktow = SrcX * SrcY; double sumaR = 0, sumaG = 0, sumaB = 0; for (i = 0; i < 256; i++) { DB[i] = sumaB + HB[i] / ilosc_punktow; DG[i] = sumaG + HG[i] / ilosc_punktow; DR[i] = sumaR + HR[i] / ilosc_punktow; sumaB = DB[i]; sumaG = DG[i]; sumaR = DR[i]; }; Dispatcher.BeginInvoke(DispatcherPriority.Background, (SendOrPostCallback)delegate { progressBar1.Maximum = SrcY - 1; }, null); // aktualizuj bitmape for (y = 0; y < SrcY; y++) { for (x = 0; x < SrcX; x++) { var stmp = src.GetPixel(x, y); var val = new WpfImage.MyToolkit.RGBColor() { B = (byte)(DB[stmp.B] * 255), G = (byte)(DG[stmp.G] * 255), R = (byte)(DR[stmp.R] * 255) }; src.SetPixel(x, y, val); }; Dispatcher.BeginInvoke(DispatcherPriority.Background, (SendOrPostCallback)delegate { progressBar1.Value = y; }, null); } } 

5)希望它能certificate这一点。

WriteableBitmap明确支持线程。 但是你必须遵循协议,使用线程中的Lock()方法来访问BackBuffer。