c# wpf Актуализиране на източника на потребителския интерфейс от BlockingCollection с Dispatcher

Ето моя проблем.
Зареждам няколко BitmapImages в 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);
        }
    }

debug обвинява, че грешката е тук

                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
Здравей Клеменс, премахнах Sleep, но все още не работи. Странен факт е, че когато извикам Load Thread за втори път, изображенията се показват. Но след като извикам функцията за показване, нищо не се случва. Така че първия път, когато стартирам програмата, се показва само последното изображение. Отсега нататък всеки път, когато извикам Load Thread, изображенията се показват и когато извикам Display Thread, нищо не се случва. - 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