CiucaS
CiucaS

Reputation: 2128

Use BitmapDecoder in an other thread then where is created. (The calling thread cannot access this object because a different thread owns it.)

So, i have a function that will load an image from disk async in an other thread ( big images will be loaded and I don't want the UI THread to be locked while loading).

Loading is done like this

  public override void LoadFile()
        {
            using (var imageStream = new FileStream(_filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                Decoder = new TiffBitmapDecoder(imageStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
                InitializeFile();
            }
        }

Then I want to use the Decoder on the main thread

   public List<ThumbnailModel> LoadPages()
        {
            var result = new List<ThumbnailModel>();

            foreach (var frame in Decoder.Frames) <--// this line throws exception
            {
                result.Add(new ThumbnailModel
                {
                    Name = _metadataLoader.GetPageName((BitmapMetadata)frame.Metadata),
                    Bitmap = new WriteableBitmap(frame)
                });
            }

            return result;
        }

Now here is the problem, whenever I reach the line where I try to access the Decoder.Frames it throws exception (The calling thread cannot access this object because a different thread owns it.)

Is there a way I can use my Decoder in the main thread if not, the only possible solution is to load all the image information in the other thread?

Full code version : // this is the task, that calls the imageFactory LoadFile method - NewThread

  private async Task OpenFileAsync(string strFilePath)
            {
                var newFile = _imageFileFactory.LoadFile(strFilePath);

            if (newFile != null)
            {
                _imagefile = newFile;
            }
        }
  //image factory load file - NewThread
  public IImageFile LoadFile(string filePath)
        {
            if (string.IsNullOrWhiteSpace(filePath))
            {
                return null;
            }

            var fileExtension = Path.GetExtension(filePath); // .tiff or .jpeg

            var file = new ImageFileTiff(filePath, _metatadaFactory, _metadataVersioner);

            file.LoadFile();


            return file;
        }
// ImageFileTiff LoadFile will create a decoder - NewThread
 public override void LoadFile()
        {
            using (var imageStream = new FileStream(_filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                Decoder = new JpegBitmapDecoder(imageStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);

                InitializeFile();
            }
        }

After we have an IImageFile we call on MainThread(UIThread)

var pages = _imagefile.LoadPages(); 

Where LoadPages is the place where the app breaks. also called on UIThread public List LoadPages() { var result = new List();

        foreach (var frame in Decoder.Frames)
        {
            result.Add(new ThumbnailModel
            {
                Name = _metadataLoader.GetPageName((BitmapMetadata)frame.Metadata),
                Bitmap = new WriteableBitmap(frame)
            });
        }

        return result;
    }

Upvotes: 0

Views: 195

Answers (1)

Ostas
Ostas

Reputation: 949

I thought you could simply return the decoder from the thread to be able to access it but your decoder is a TiffBitmapDecoder which inherits from DispatcherObject (https://learn.microsoft.com/en-gb/dotnet/api/system.windows.threading.dispatcherobject?view=netcore-3.1).

So you won't be able to access it from a different thread than the one where it was created msdn:"Only the thread that the Dispatcher was created on may access the DispatcherObject directly"

What you could do instead is use the decoder in it's thread and return the final result:

I couldn't build on your sample since there was to much missing for me to test it but I built a similar project to give an exemple:

public partial class MainWindow : Window
{
    public MainWindow()
    {

    }

    public TiffBitmapDecoder LoadFile()
    {
        OpenFileDialog openFileDialog = new OpenFileDialog();
        openFileDialog.InitialDirectory = "c:\\";
        openFileDialog.Filter = "tiff files (*.tif)|*.tif|All files (*.*)|*.*";
        openFileDialog.FilterIndex = 2;
        openFileDialog.RestoreDirectory = true;

        if (openFileDialog.ShowDialog() == true && !string.IsNullOrEmpty(openFileDialog.FileName))
        {
            //I didn't bother to check the file extension since it's just an exemple
            using (var imageStream = openFileDialog.OpenFile())
            {
                return new TiffBitmapDecoder(imageStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
            }
        }
        else
        {
            //User cancelled
            return null;
        }
    }

    public List<ThumbnailModel> LoadPages(TiffBitmapDecoder decoder)
    {
        //TiffBitmapDecoder" inherits from DispatcherObject/>
        //https://learn.microsoft.com/en-gb/dotnet/api/system.windows.threading.dispatcherobject?view=netcore-3.1
        var result = new List<ThumbnailModel>();
        if (decoder != null)
        {
            try
            {
                foreach (var frame in decoder.Frames)
                {
                    result.Add(new ThumbnailModel
                    {
                        //set the variables
                    });
                }
            }
            catch(InvalidOperationException e)
            {
                MessageBox.Show(e.Message, "Error");
            }
        }
        else
        {
            //Nothing to do
        }
        return result;
    }

    private async Task AsyncLoading()
    {
        this.thumbnailModels = await Task.Run<List<ThumbnailModel>>(() =>
        {
            var decoder = this.LoadFile();
            return this.LoadPages(decoder);
        });
    }

    private List<ThumbnailModel> thumbnailModels = null;

    private async void AsyncLoadingButton_Click(object sender, RoutedEventArgs e)
    {
        await this.AsyncLoading();
    }
}

public class ThumbnailModel
{
}

Content of MainWindow.xaml just in case:

<Grid>
    <StackPanel Orientation="Vertical">
        <Button x:Name="NoReturnButton" Margin="10" HorizontalAlignment="Center" Content="Call AsyncLoadingNoReturn" Click="AsyncLoadingButton_Click" />
    </StackPanel>
</Grid>

Upvotes: 1

Related Questions