c# wpf Обновление источника пользовательского интерфейса из BlockingCollection с помощью Dispatcher

Вот моя проблема.
Я загружаю несколько изображений BitmapImage в BlockingCollection.

    public void blockingProducer(BitmapImage imgBSource)
    {
        if (!collection.IsAddingCompleted)
            collection.Add(imgBSource);
    }

загрузка происходит в фоновом потоке.

    private void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        String filepath; int imgCount = 0;

        for (int i = 1; i < 10; i++)
        {
            imgCount++;

            filepath = "Snap";
            filepath += imgCount;
            filepath += ".bmp";

            this.Dispatcher.BeginInvoke(new Action(() =>
            {
                label1.Content = "Snap" + imgCount + " loaded.";
            }), DispatcherPriority.Normal);

            BitmapImage imgSource = new BitmapImage();
            imgSource.BeginInit();
            imgSource.UriSource = new Uri(filepath, UriKind.Relative);
            imgSource.CacheOption = BitmapCacheOption.OnLoad;
            imgSource.EndInit();

            blockingProducer(imgSource);
        }
    }

при отладке этой части кода все выглядит нормально, теперь возникает проблема...

после завершения загрузки изображений я хочу показать их в пользовательском интерфейсе одно за другим. Для этого я использую диспетчер, но всегда получаю сообщение о том, что вызываемый поток не может получить доступ к объекту, поскольку он принадлежит другому потоку.

    public void display(BlockingCollection<BitmapImage> results)
    {
        foreach (BitmapImage item in collection.GetConsumingEnumerable())
        {
            this.Dispatcher.BeginInvoke(new Action(() =>
            {
                this.dstSource.Source = item;
                Thread.Sleep(250);
            }), DispatcherPriority.Background);
        }
    }

отладка обвиняет, что ошибка здесь

                this.dstSource.Source = item;

Я пробую все, но не могу понять, что не так. У кого-нибудь есть идеи?


person Probst    schedule 18.04.2013    source источник


Ответы (2)


Вы должны вызвать Freeze после загрузки изображений, чтобы сделать их доступными в другие темы:

BitmapImage imgSource = new BitmapImage();
imgSource.BeginInit();
imgSource.UriSource = new Uri(filepath, UriKind.Relative);
imgSource.CacheOption = BitmapCacheOption.OnLoad;
imgSource.EndInit();
imgSource.Freeze(); // here

Насколько я понял, флаг BitmapCacheOption.OnLoad эффективен только тогда, когда BitmapImage загружается из потока. Раздел «Примечания» в BitmapCacheOption говорит:

Установите для CacheOption значение BitmapCacheOption.OnLoad, если вы хотите закрыть поток, используемый для создания BitmapImage. Параметр кэша OnDemand по умолчанию сохраняет доступ к потоку до тех пор, пока изображение не понадобится, а сборщик мусора выполняет очистку.

BitmapImage, созданный из Uri, может быть загружен асинхронно (см. IsDownloading). Следовательно, Freeze может не вызываться для такого BitmapImage, так как загрузка может продолжаться после EndInit. Я предполагаю, что это все же работает в вашем случае, потому что вы загружаете BitmapImages из файла Uris, что, кажется, делается немедленно.

Чтобы избежать этой потенциальной проблемы, вы можете просто создать BitmapImage из FileStream:

var imgSource = new BitmapImage();

using (var stream = new FileStream(filepath, FileMode.Open))
{
    imgSource.BeginInit();
    imgSource.StreamSource = stream;
    imgSource.CacheOption = BitmapCacheOption.OnLoad;
    imgSource.EndInit();
    imgSource.Freeze();
}
person Clemens    schedule 18.04.2013
comment
Привет, спасибо, это работает. Последний вопрос. После загрузки изображений в BlockingCollection я начинаю их отображать, но отображается только последнее изображение. Любая идея о том, что происходит? Спасибо. - person Probst; 18.04.2013
comment
Это, безусловно, потому, что вы выполняете вызов Sleep внутри метода делегата, который передается Dispatcher. Вы должны были бы вызвать его перед BeginInvoke. Однако лучшим решением было бы использовать DispatcherTimer для отображения изображений по одному. - person Clemens; 18.04.2013
comment
Привет Клеменс, я удалил сон, но он все еще не работает. Странным фактом является то, что когда я вызываю поток загрузки во второй раз, изображения отображаются. Но как только я вызываю функцию отображения, ничего не происходит. Поэтому при первом запуске программы отображается только последнее изображение. С этого момента каждый раз, когда я вызываю поток загрузки, изображения отображаются, и когда я вызываю поток отображения, ничего не происходит. - person Probst; 18.04.2013
comment
Попробуйте вытащить и отобразить следующее изображение в DispatcherTimer. - person Clemens; 18.04.2013
comment
Привет, Клеменс. Я попробовал DispatcherTimer, и теперь ничего не работает. Я разместил код ниже. Есть идеи? Спасибо. - person Probst; 18.04.2013

Для дальнейших будущих читателей, вот код, который я использовал для решения моей проблемы.

    public void display(BlockingCollection<BitmapImage> collection)
    {
        if (collection.IsCompleted || collection.Count != 0)
        {
            BitmapImage item = collection.Take();
            this.Dispatcher.BeginInvoke(new Action(() =>
            {
                this.dstSource.Source = item;

            }), DispatcherPriority.Normal);
        }
        else
        {
            dispatcherTimer.Stop();
        }
    }

    public void dispatcherTimer_Tick(object sender, EventArgs e)
    {
        display(collection);
    }

    public void configureDispatcherTimer()
    {
        dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
        TimeSpan interval = TimeSpan.FromMilliseconds(150);
        dispatcherTimer.Interval = interval;
    }
person Probst    schedule 18.04.2013