简体   繁体   中英

How to pass BitmapImage reference to BackgroundWorker.DoWork

I have an BitmapImage constructed from resource stream. I need to send it to background worker thread for some post processing. But it seems that the worker thread can't access bitmap data and gets System.UnauthorizedAccessException in my LoadImage first line. How to solve this? Also I need to transfer the processed bitmap back for UI (XAML) to display. How to do that correctly?

    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 needs be called from the UI thread. It's just a limitation in that utility you can't get around.

From MSDN:

The WriteableBitmap class uses two buffers. The back buffer is allocated in system memory and accumulates content that is not currently displayed. The front buffer is allocated in system memory and contains the content that is currently displayed. The rendering system copies the front buffer to video memory for display.

Two threads use these buffers. The user interface (UI) thread generates the UI but does not present it to the screen. The UI thread responds to user input, timers, and other events. An application can have multiple UI threads. The render thread composes and renders changes from the UI thread. There is only one render thread per application.

The UI thread writes content to the back buffer. The render thread reads content from the front buffer and copies it to video memory. Changes to the back buffer are tracked with changed rectangular regions.

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

Alternative to BackgroundWorker:

There is no getting around the fact you need to do this on the UI thread. But you can push it to the end of the queue so that it might be less noticable to the user.

Try something like this:

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

This should be enough so that your image processing (in this case the action that is passed) will be put at the end of the queue that is waiting to be processed on the UI thread. Your image processing will still block the UI thread while it is working, but at least everything else should be done first.

How it works:

When you call BeginInvoke, your action is placed at the end of the dispatcher's queue, to be run when the UI thread is freed up from what it is currently doing (running your code). But because you could call BeginInvoke again (and again and again) and then your image processing would block the other things in the dispatcher's queue, we want to put it at the back again. This is where the DispatcherTimer comes in. From MSDN:

Timers are not guaranteed to execute exactly when the time interval occurs, but they are guaranteed to not execute before the time interval occurs. This is because DispatcherTimer operations are placed on the Dispatcher queue like other operations. When the DispatcherTimer operation executes is dependent on the other jobs in the queue and their priorities.

So just the one millisecond interval we give it should be enough to shove this right back to the end of the queue.

On Windows Phone 7, this may still block some touch events, but at least you won't have blocked your page from rendering.

On Windows Phone 8, the Panorama, Pivot, and LongListSelector all respond to input on a non UI thread, so you would be a little safer there.

This worked for me. I freeze the bitmap before passing it to backgroundWorker and no exception is thrown.

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

And when i finish with the bitmap in backgroundworker i freeze the bitmap.

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

And i Update the Bitmap image in 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

I will have to disagree with bitmap modification being a UI only process. I have implemented some code(perhaps too long to post) that calculates modification parameters for separate bitmaps inside separate backgroundworkers, storing data in some arrays(for pixel allocation) when calculations complete and setting some booleans to act as "while" and "if" switches. Inside another BGW, one for each bitmap to process, I have a loop waiting for the calculation-completion boolean to trigger. When triggered, the secondary workers copy the results from the arrays then restart the calculation thread to begin calculations for the next frame... (Did I mention this is an animation??). These BGWs then move on to apply the copied parameters to temporary bitmaps (one for each original bitmap) using tempbit.SetPixel(x,y,color) //Note: (There might be a more efficiant method to use...). Once the bitmaps are updated, more boolean switches get thrown(all-the-while the calculation BGWs are cranking away on the next frame). Then on a final BGW, I implement code to wait for every separate temperary bitmap to update. When they do, I copy the bitmaps, and restart the image-updating BGWs(this boolean structure prevents any object overlap). Once all images are copied, I combine the images, then update a picturebox.Image then restart the loop( this will require some more boolean logic and a goto statement or a restart async relay method). So basically, so long as no two backgroundworker objects are allowed to access a shared third object, no exceptions are thrown.

In laymans terms, I have separate BGWs for wach separate task, and implement logic to prevent any thread overlap. This still allows the async advantage of the BGW class, without all the fuss of cross-threading and busy checking.

Happy Coding!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM