【问题标题】:Asynchronous operations on WriteableBitmapWriteableBitmap 上的异步操作
【发布时间】:2009-12-06 12:48:57
【问题描述】:

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

我有以下代码:

public class ImageProcessor
{
    public Collection<WriteableBitmap> 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
    }
}

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

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

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

那么我怎样才能在另一个线程中访问位图的数据(最好使用 BackgroundWorker)?

我不知道,也许我的整个方法都是错误的。我想要实现的总体思路是:

  1. 用户加载位图,然后将其显示在窗口中。
  2. 用户单击按钮并在位图上执行长时间操作,但 UI 是响应式的(例如允许用户取消操作),并且进度条会报告进度。

提前感谢您的帮助。

【问题讨论】:

    标签: c# multithreading asynchronous writeablebitmap


    【解决方案1】:

    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 R G B
            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) 希望它能证明这一点。

    【讨论】:

      【解决方案2】:

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

      【讨论】:

        猜你喜欢
        • 2023-03-16
        • 2013-01-30
        • 1970-01-01
        • 1970-01-01
        • 2018-01-04
        • 1970-01-01
        • 2020-10-25
        • 1970-01-01
        • 2016-06-17
        相关资源
        最近更新 更多