Асинхронная загрузка BitmapImage в C # с использованием WPF

Какой лучший способ асинхронно загрузить BitmapImage в C # с помощью WPF?


person user3837    schedule 31.08.2008    source источник


Ответы (5)


Я просто изучал это и должен был вложить свои два цента, хотя через несколько лет после первоначального сообщения (на случай, если кто-то еще придет искать то же самое, что и я).

У меня есть элемент управления Изображение, изображение которого необходимо загрузить в фоновом режиме с помощью потока, а затем отобразить.

Проблема, с которой я постоянно сталкивался, заключалась в том, что BitmapSource, источник Stream и элемент управления Image должны были находиться в одном потоке.

В этом случае использование Binding и установка IsAsynch = true вызовет исключение между потоками.

BackgroundWorker отлично подходит для WinForms, и вы можете использовать его в WPF, но я предпочитаю избегать использования сборок WinForm в WPF (раздувание проекта не рекомендуется, и это тоже хорошее практическое правило). Это должно вызвать исключение недопустимой перекрестной ссылки и в этом случае, но я не проверял его.

Оказывается, одна строка кода выполнит любое из этих действий:

//Create the image control
Image img = new Image {HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, VerticalAlignment = System.Windows.VerticalAlignment.Stretch};

//Create a seperate thread to load the image
ThreadStart thread = delegate
     {
         //Load the image in a seperate thread
         BitmapImage bmpImage = new BitmapImage();
         MemoryStream ms = new MemoryStream();

         //A custom class that reads the bytes of off the HD and shoves them into the MemoryStream. You could just replace the MemoryStream with something like this: FileStream fs = File.Open(@"C:\ImageFileName.jpg", FileMode.Open);
         MediaCoder.MediaDecoder.DecodeMediaWithStream(ImageItem, true, ms);

         bmpImage.BeginInit();
         bmpImage.StreamSource = ms;
         bmpImage.EndInit();

         //**THIS LINE locks the BitmapImage so that it can be transported across threads!! 
         bmpImage.Freeze();

         //Call the UI thread using the Dispatcher to update the Image control
         Dispatcher.BeginInvoke(new ThreadStart(delegate
                 {
                         img.Source = bmpImage;
                         img.Unloaded += delegate 
                                 {
                                         ms.Close();
                                         ms.Dispose();
                                 };

                          grdImageContainer.Children.Add(img);
                  }));

     };

//Start previously mentioned thread...
new Thread(thread).Start();
person Brian    schedule 20.04.2010
comment
BackgroundWorker определен в пространстве имен System.ComponentModel в System.dll, а не в Windows Forms, поэтому его можно использовать для WPF. - person Samuel Jack; 10.01.2011
comment
Использование BackgroundWorker или ThreadStart требует слишком большого количества кода. Просто бросьте ThreadPool.QueueUserWorkItem(_ => { ... }) и вот ваша ветка; - person SandRock; 28.01.2013

Предполагая, что вы используете привязку данных, установка Binding .IsAsync в True кажется стандартным способом добиться этого. Если вы загружаете растровое изображение в файл кода программной части, используя фоновый поток, + объект Dispatcher - это распространенный способ асинхронного обновления пользовательского интерфейса.

person aku    schedule 31.08.2008
comment
bitmapImage должен быть создан в потоке пользовательского интерфейса: это вызовет исключение ... - person Jonathan ANTOINE; 18.08.2011
comment
@ Jmix90: непонятно, о какой части ответа вы говорите. Однако: использование Binding.IsAsync позволяет указывать Uri / путь в Image.Source и автоматически загружать изображение асинхронно. WPF позаботится о любых проблемах с перекрестными потоками в этом сценарии. Если вы создаете растровые изображения в фоновом потоке, нужно просто вызвать Freeze() для объекта растрового изображения, прежде чем передать его потоку пользовательского интерфейса. Он вызывает исключение только в том случае, если вы делаете это неправильно. - person Peter Duniho; 08.03.2017

Это позволит вам создать BitmapImage в потоке пользовательского интерфейса, используя HttpClient для асинхронной загрузки:

private async Task<BitmapImage> LoadImage(string url)
{
    HttpClient client = new HttpClient();

    try
    {
        BitmapImage img = new BitmapImage();
        img.CacheOption = BitmapCacheOption.OnLoad;
        img.BeginInit();
        img.StreamSource = await client.GetStreamAsync(url);
        img.EndInit();
        return img;
    }
    catch (HttpRequestException)
    {
        // the download failed, log error
        return null;
    }
}
person David    schedule 09.06.2016

Чтобы подробнее рассказать об ответе aku, вот небольшой пример того, где установить IsAsync:

ItemsSource="{Binding IsAsync=True,Source={StaticResource ACollection},Path=AnObjectInCollection}"

Это то, что вы сделали бы в XAML.

person kevindaub    schedule 23.04.2009
comment
Я не понимаю, почему этот ответ получил -1, ведь это было именно то, что я искал! Не могу понять, как выполнить асинхронное преобразование. Оказывается, вам просто нужно указать IsAsync = True в вашей привязке. Большое тебе спасибо! - person Rasive; 13.04.2015

Используйте или расширьте System.ComponentModel.BackgroundWorker:
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx

Лично я считаю, что это самый простой способ выполнять асинхронные операции в клиентских приложениях. (Я использовал это в WinForms, но не в WPF. Я предполагаю, что это будет работать и в WPF.)

Я обычно расширяю Backgroundworker, но вам это не обязательно.

public class ResizeFolderBackgroundWorker : BackgroundWorker
{

    public ResizeFolderBackgroundWorker(string sourceFolder, int resizeTo)
    {
        this.sourceFolder = sourceFolder;
        this.destinationFolder = destinationFolder;
        this.resizeTo = resizeTo;

        this.WorkerReportsProgress = true;
        this.DoWork += new DoWorkEventHandler(ResizeFolderBackgroundWorker_DoWork);
    }

    void ResizeFolderBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        DirectoryInfo dirInfo = new DirectoryInfo(sourceFolder);
        FileInfo[] files = dirInfo.GetFiles("*.jpg");


        foreach (FileInfo fileInfo in files)
        {
            /* iterate over each file and resizing it */
        }
    }
}

Вот как вы бы использовали его в своей форме:

    //handle a button click to start lengthy operation
    private void resizeImageButtonClick(object sender, EventArgs e)
    {
        string sourceFolder = getSourceFolderSomehow();
        resizer = new ResizeFolderBackgroundWorker(sourceFolder,290);
        resizer.ProgressChanged += new progressChangedEventHandler(genericProgressChanged);
        resizer.RunWorkerCompleted += new RunWorkerCompletedEventHandler(genericRunWorkerCompleted);

        progressBar1.Value = 0;
        progressBar1.Visible = true;

        resizer.RunWorkerAsync();
    }

    void genericRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        progressBar1.Visible = false;
        //signal to user that operation has completed
    }

    void genericProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        progressBar1.Value = e.ProgressPercentage;
        //I just update a progress bar
    }
person bentford    schedule 02.09.2008