如何将BitmapImage引用传递给BackgroundWorker.DoWork
本文关键字:BackgroundWorker DoWork 引用 BitmapImage | 更新日期: 2023-09-27 17:50:17
我有一个从资源流构造的BitmapImage。我需要将其发送到后台工作线程进行一些后处理。但似乎工作线程无法访问位图数据并获得系统。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;
}
WriteableBitmap需要从UI线程调用。这只是那个实用程序的一个限制,你无法绕过它。
从MSDN:http://msdn.microsoft.com/en-us/library/system.windows.media.imaging.writeablebitmap.aspxWriteableBitmap类使用两个缓冲区。回缓冲区在系统内存中分配,并累积当前未显示的内容。前缓冲区在系统内存中分配,包含当前显示的内容。呈现系统将前缓冲区复制到显存中以供显示。
两个线程使用这些缓冲区。用户界面(UI)线程生成UI,但不将其呈现在屏幕上。UI线程响应用户输入、计时器和其他事件。一个应用程序可以有多个UI线程。呈现线程合成并呈现来自UI线程的更改。每个应用程序只有一个渲染线程。
UI线程将内容写入回缓冲区。渲染线程从前端缓冲区读取内容并将其复制到显存。对后缓冲区的更改将随着矩形区域的更改而跟踪。
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(一次又一次)然后你的图像处理会阻塞dispatcher队列中的其他东西,我们想要再次把它放在后面。这就是DispatcherTimer发挥作用的地方。从MSDN:
定时器不保证正好在时间间隔发生的时候执行,但可以保证在时间间隔发生之前不执行。这是因为DispatcherTimer操作像其他操作一样放在Dispatcher队列上。DispatcherTimer操作何时执行取决于队列中的其他作业及其优先级。
所以我们给它的一毫秒间隔应该足以把这个推回到队列的末尾。
在Windows Phone 7上,这可能仍然会阻止一些触摸事件,但至少你不会阻止页面渲染。
在Windows Phone 8上,全景、枢轴和LongListSelector都在非UI线程上响应输入,所以你在那里会更安全。
我不同意位图修改只是一个UI过程。我已经实现了一些代码(可能太长,不能发布),计算修改参数为单独的后台工作中的单独位图,在一些数组中存储数据(用于像素分配),当计算完成并设置一些布尔值作为"while"answers"if"开关。在另一个BGW中,每个要处理的位图对应一个BGW,我有一个等待计算完成布尔值触发的循环。当触发时,次要工作程序从数组中复制结果,然后重新启动计算线程开始下一帧的计算…(我有没有提到这是一部动画??)这些BGWs然后继续使用tempbit.SetPixel(x,y,color)//将复制的参数应用于临时位图(每个原始位图对应一个)。一旦位图被更新,就会抛出更多的布尔开关(与此同时,计算bgw会在下一帧上运行)。然后在最后一个BGW上,我实现了等待每个单独的临时位图更新的代码。当他们这样做时,我复制位图,并重新启动图像更新bgw(这个布尔结构防止任何对象重叠)。复制完所有图像后,我将合并这些图像,然后更新一个图片框。Image然后重新启动循环(这将需要一些布尔逻辑和goto语句或restart异步中继方法)。所以基本上,只要不允许两个后台工作者对象访问共享的第三个对象,就不会抛出异常。
通俗地说,我为每个单独的任务都有单独的bgw,并实现逻辑以防止任何线程重叠。这仍然允许BGW类的异步优势,没有所有的跨线程和繁忙的检查的麻烦。
编码快乐!
这对我很有效。在将位图传递给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;
}
和i更新位图图像在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