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

13

如何在使用WPF的C#中异步加载BitmapImage?

6个回答

7

我在研究这个问题时想要发表一下自己的看法,虽然是在原帖几年后(以防其他人寻找与我相同的信息)。

我有一个需要使用流加载其图像并显示的图像控件。

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

在这种情况下,使用绑定并将其IsAsynch = true设置为true会抛出跨线程异常。

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

事实证明,只需一行代码即可使这些工作:

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

7
BackgroundWorker 定义在 System.dll 的 System.ComponentModel 命名空间中,而不是 Windows Forms 中,因此在 WPF 中使用它是可以的。 - Samuel Jack
使用BackgroundWorker或ThreadStart需要编写太多代码。只需抛出ThreadPool.QueueUserWorkItem(_ => { ... }),就可以创建一个线程了; - SandRock

3
这将允许您通过使用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;
    }
}

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

4
位图图像必须在UI线程上创建:否则会引发异常... - Jonathan ANTOINE
1
@Jmix90:不清楚你指的是回答的哪一部分。不过,使用 Binding.IsAsync 允许在 Image.Source 中指定 Uri/path 并自动异步加载图像。WPF 会处理此场景中的任何跨线程问题。如果在后台线程中创建位图,则只需在将其移交给 UI 线程之前在位图对象上调用 Freeze() 即可。只有在操作错误时才会抛出异常。 - Peter Duniho

2
为了进一步解释Aku的回答,这里提供一个小例子,说明在哪里设置IsAsync:
ItemsSource="{Binding IsAsync=True,Source={StaticResource ACollection},Path=AnObjectInCollection}"

那就是在XAML中你需要做的事情。

我不明白为什么这个答案得了-1分,因为它正是我在寻找的!一直无法弄清如何进行异步转换,结果你只需要在绑定中加入IsAsync=True就可以了。非常感谢! - Rasive

2
 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); 

}

0

使用或扩展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
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接