Используйте BitmapDecoder в другом потоке, в котором он был создан. (Вызывающий поток не может получить доступ к этому объекту, потому что он принадлежит другому потоку.) - PullRequest
0 голосов
/ 29 мая 2020

Итак, у меня есть функция, которая загружает изображение с диска asyn c в другой поток (будут загружены большие изображения, и я не хочу, чтобы поток пользовательского интерфейса был заблокирован во время загрузки).

Загрузка выполняется следующим образом

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

Затем я хочу использовать декодер в основном потоке

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

Теперь вот проблема, когда я достигаю Строка, в которой я пытаюсь получить доступ к Decoder.Frames, выдает исключение (вызывающий поток не может получить доступ к этому объекту, потому что он принадлежит другому потоку).

Есть ли способ использовать свой декодер в основном потоке, если нет , единственное возможное решение - загрузить всю информацию об изображении в другой поток?

Полная версия кода: // это задача, которая вызывает метод ImageFactory LoadFile - 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();
            }
        }

После того, как у нас есть IImageFile, мы вызываем MainThread (UIThread)

var pages = _imagefile.LoadPages(); 

Где LoadPages - это место, где приложение ломается. также вызывается в UIThread publi c 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;
    }

1 Ответ

1 голос
/ 29 мая 2020

Я думал, вы можете просто вернуть декодер из потока, чтобы получить к нему доступ, но ваш декодер - это TiffBitmapDecoder, который наследуется от DispatcherObject (https://docs.microsoft.com/en-gb/dotnet/api/system.windows.threading.dispatcherobject?view=netcore-3.1).

Итак, вы не сможет получить к нему доступ из потока, отличного от того, в котором он был создан msdn: " Только поток, в котором был создан Dispatcher, может напрямую обращаться к DispatcherObject "

What вместо этого вы могли бы использовать декодер в потоке и вернуть окончательный результат:

Я не смог построить на вашем образце, так как мне не хватало многого, чтобы проверить его, но я создал аналогичный проект, чтобы дать пример:

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://docs.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
{
}

Содержимое MainWindow.xaml на всякий случай:

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