如何将BitmapImage引用传递给BackgroundWorker.DoWork

时间:2012-12-31 05:29:01

标签: c# windows-phone-7 backgroundworker pass-by-reference

我有一个从资源流构造的BitmapImage。我需要将它发送到后台工作线程进行一些后期处理。但似乎工作线程无法访问位图数据并在我的LoadImage第一行中获得 System.UnauthorizedAccessException 。怎么解决这个?此外,我需要将处理后的位图传回UI(XAML)进行显示。怎么做到这一点?

    ImageLoader.RunWorkerAsync(albumArtImage);

    private void LoadImage(object sender, DoWorkEventArgs e)
    {
        WriteableBitmap wb = new WriteableBitmap((BitmapImage)e.Argument);
        wb.Resize(AppWidth, AppHeight, WriteableBitmapExtensions.Interpolation.Bilinear);
        var wb2 = WriteableBitmapExtensions.Convolute(wb, WriteableBitmapExtensions.KernelGaussianBlur3x3);            
        e.Result = wb2;
    }

    private void LoadImageCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        AlbumBackground.ImageSource = (WriteableBitmap)e.Result;
    }

3 个答案:

答案 0 :(得分:1)

需要从UI线程调用WriteableBitmap。这只是你无法解决的实用程序的限制。

来自MSDN:

  

WriteableBitmap类使用两个缓冲区。后台缓冲区在系统内存中分配,并累积当前未显示的内容。前缓冲区在系统内存中分配,包含当前显示的内容。渲染系统将前缓冲区复制到视频内存以供显示。

     

两个线程使用这些缓冲区。用户界面(UI)线程生成UI但不将其呈现给屏幕。 UI线程响应用户输入,计时器和其他事件。应用程序可以有多个UI线程。渲染线程组成并呈现来自UI线程的更改。每个应用程序只有一个渲染线程。

     

UI线程将内容写入后台缓冲区。渲染线程从前端缓冲区读取内容并将其复制到视频内存。使用更改的矩形区域跟踪对后缓冲区的更改。

http://msdn.microsoft.com/en-us/library/system.windows.media.imaging.writeablebitmap.aspx

替代BackgroundWorker:

您无需在UI线程上执行此操作。但是你可以把它推到队列的末尾,这样对用户来说就不那么明显了。

尝试这样的事情:

public static void RunDelayedOnUiThread(Action action)
{
     Deployment.Current.Dispatcher.BeginInvoke(() =>
     {
         var timer = new DispatcherTimer 
                     { 
                         Interval = TimeSpan.FromMilliseconds(1) 
                     };
         timer.Tick += (sender, args) =>
         {
             timer.Stop();
             action();
         };

         timer.Start();
     });
}

这应该足以使您的图像处理(在这种情况下是传递的操作)将放在等待在UI线程上处理的队列的末尾。您的图像处理仍然会阻止UI线程正常工作,但至少应该先完成其他所有操作。

工作原理:

当您调用BeginInvoke时,您的操作将放置在调度程序队列的末尾,以便在UI线程从当前正在执行的操作(运行代码)中释放时运行。但是因为您可以再次调用BeginInvoke(并且一次又一次),然后您的图像处理将阻止调度程序队列中的其他内容,我们希望将其再次放在后面。这就是DispatcherTimer的用武之地。来自MSDN:

  

不保证定时器在时间间隔发生时准确执行,但保证在时间间隔发生之前不执行定时器。这是因为DispatcherTimer操作与其他操作一样放在Dispatcher队列中。 DispatcherTimer操作执行时依赖于队列中的其他作业及其优先级。

因此,我们给它的一毫秒间隔应该足以将其推回到队列的末尾。

在Windows Phone 7上,这可能仍会阻止某些触摸事件,但至少您不会阻止您的网页呈现。

在Windows Phone 8上,Panorama,Pivot和LongListSelector都响应非UI线程上的输入,因此您可以更安全一些。

答案 1 :(得分:0)

这对我有用。我在将位图传递给backgroundWorker之前将其冻结,并且不会抛出任何异常。

BackgroundWorker bw = new BackgroundWorker();
BitmapSource bmpCanvas;
BitmapSource bmpRef;

List<object> arg = new List<object>();
arg.Add(bmpCanvas);
arg.Add(bmpRef);
bmpCanvas.Freeze();
bmpRef.Freeze();

bw.RunWorkerAsync(arg);

当我在backgroundworker中完成位图时,我会冻结位图。

void bw_DoWork(object sender, DoWorkEventArgs e)
{
   List<object> argu = e.Argument as List<object>;

   BitmapSource bCanvas = (BitmapSource)argu[0];
   BitmapSource bref = (BitmapSource)argu[1];
   // doing something with those images...
   bCanvas.Freeze();
   e.Result = bCanvas;
}

我在RunWorkerCompleted中更新位图图像。

void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
   BitmapSource bmpCanvas = (BitmapSource)e.Result;

   image.Source = bmpCanvas;
}

http://msdn.microsoft.com/en-us/library/ms750509.aspx

答案 2 :(得分:0)

我不得不不同意位图修改是一个仅限UI的过程。我已经实现了一些代码(可能太长而无法发布),它们计算单独的后台工作者中单独位图的修改参数,在计算完成时将数据存储在某些数组中(用于像素分配)并将一些布尔值设置为“while”和“if”开关。在另一个BGW中,每个位图要处理一个,我有一个循环等待计算完成布尔值来触发。当触发时,辅助工作者从数组中复制结果然后重新启动计算线程以开始计算下一帧...(我是否提到过这是一个动画?)。然后,这些BGW继续使用tempbit.SetPixel(x,y,color)将复制的参数应用于临时位图(每个原始位图一个)//注意:(可能有更有效的方法使用...)。一旦位图更新,就会抛出更多的布尔开关(同时计算BGW在下一帧开始时)。然后在最终的BGW上,我实现代码等待每个单独的温度位图更新。当他们这样做时,我复制位图,并重新启动图像更新BGW(这个布尔结构可以防止任何对象重叠)。复制完所有图像后,我合并图像,然后更新picturebox.Image然后重新启动循环(这将需要更多的布尔逻辑和goto语句或重新启动异步中继方法)。所以基本上,只要不允许两个backgroundworker对象访问共享的第三个对象,就不会抛出任何异常。

在外行人看来,我有单独的BGW用于wach单独任务,并实现逻辑以防止任何线程重叠。这仍然允许BGW类的异步优势,而不必担心跨线程和忙碌检查。

快乐的编码!