使用 WPF 在 C# 中异步加载 BitmapImage

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/36748/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 09:43:51  来源:igfitidea点击:

Asynchronously Loading a BitmapImage in C# using WPF

提问by user3837

What's the best way to asynchronously load an BitmapImage in C# using WPF?

使用 WPF 在 C# 中异步加载 BitmapImage 的最佳方法是什么?

回答by aku

Assuming you're using data binding, setting Binding.IsAsyncproperty to True seems to be a standard way to achieve this. If you're loading the bitmap in the code-behind file using background thread + Dispatcher object is a common way to update UI asynchronous

假设您正在使用数据绑定,将Binding.IsAsync属性设置为 True 似乎是实现此目的的标准方法。如果您使用后台线程 + Dispatcher 对象在代码隐藏文件中加载位图是异步更新 UI 的常用方法

回答by bentford

Use or extend System.ComponentModel.BackgroundWorker:
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx

使用或扩展 System.ComponentModel.BackgroundWorker:http:
//msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx

Personally, I find this to be the easiest way to perform asynchronous operations in client apps. (I've used this in WinForms, but not WPF. I'm assuming this will work in WPF as well.)

就个人而言,我发现这是在客户端应用程序中执行异步操作的最简单方法。(我在 WinForms 中使用过它,但没有在 WPF 中使用过。我假设这也适用于 WPF。)

I usually extend Backgroundworker, but you dont' have to.

我通常会扩展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 */
        }
    }
}

This is how you would use it in your form:

这是您在表单中使用它的方式:

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

回答by kevindaub

To elaborate onto aku's answer, here is a small example as to where to set the IsAsync:

为了详细说明 aku 的答案,这里有一个关于在哪里设置 IsAsync 的小例子:

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

That's what you would do in XAML.

这就是您将在 XAML 中执行的操作。

回答by Brian

I was just looking into this and had to throw in my two cents, though a few years after the original post (just in case any one else comes looking for this same thing I was looking into).

我只是在研究这个并且不得不投入我的两分钱,尽管在原始帖子发布几年之后(以防万一其他人来寻找我正在寻找的同样的东西)。

I have an Imagecontrol that needs to have it's image loaded in the background using a Stream, and then displayed.

我有一个Image控件,需要使用Stream在后台加载它的图像,然后显示。

The problem that I kept running into is that the BitmapSource, it's Streamsource and the Imagecontrol all had to be on the same thread.

我一直遇到的问题是BitmapSource,它是Stream源和Image控件都必须在同一个线程上。

In this case, using a Binding and setting it's IsAsynch = true will throw a cross thread exception.

在这种情况下,使用 Binding 并将其设置为 IsAsynch = true 将引发跨线程异常。

A BackgroundWorker is great for WinForms, and you can use this in WPF, but I prefer to avoid using the WinForm assemblies in WPF (bloating of a project is not recommended, and it's a good rule of thumb too). This should throw an invalid cross reference exception in this case too, but I didn't test it.

BackgroundWorker 非常适合 WinForms,您可以在 WPF 中使用它,但我更喜欢避免在 WPF 中使用 WinForm 程序集(不推荐项目膨胀,这也是一个很好的经验法则)。在这种情况下,这也应该抛出无效的交叉引用异常,但我没有对其进行测试。

Turns out that one line of code will make any of these work:

事实证明,一行代码将使以下任何一项工作:

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

回答by Cornel Marian

 BitmapCacheOption.OnLoad

var bmp = await System.Threading.Tasks.Task.Run(() => 
{ 
BitmapImage img = new BitmapImage(); 
img.BeginInit(); 
img.CacheOption = BitmapCacheOption.OnLoad; 
img.UriSource = new Uri(path); 
img.EndInit(); 
ImageBrush brush = new ImageBrush(img); 

}

回答by David

This will allow you to create the BitmapImage on the UI thread by using the HttpClient to do the async downloading:

这将允许您通过使用 HttpClient 进行异步下载在 UI 线程上创建 BitmapImage:

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