【问题标题】:How to render a visual on another thread如何在另一个线程上呈现视觉效果
【发布时间】:2014-11-14 17:05:09
【问题描述】:

我试图在另一个线程上渲染画布。这是我的尝试:

// returns path to exported image
private string exportToImage(double width, double height, Visual visual)
{
    var filename = string.Format(@"{0}.png", Guid.NewGuid());
    var tempFile = Path.Combine(tempDir, filename);
    Rect rect = new Rect(0, 0, width, height);
    RenderTargetBitmap rtb = new RenderTargetBitmap((int)rect.Right,
        (int)rect.Bottom, 96d, 96d, System.Windows.Media.PixelFormats.Default);

    Thread RENDER_THREAD = new Thread(() => 
    {
        this.Dispatcher.Invoke((Action)(() =>
        {
            rtb.Render(visual);
        }));
    });
    RENDER_THREAD.Start();

    //endcode as PNG
    BitmapEncoder pngEncoder = new PngBitmapEncoder();
    pngEncoder.Frames.Add(BitmapFrame.Create(rtb));

    //save to memory stream
    System.IO.MemoryStream ms = new System.IO.MemoryStream();

    pngEncoder.Save(ms);
    ms.Close();
    System.IO.File.WriteAllBytes(tempFile, ms.ToArray());
    return tempFile;
}

我没有收到任何错误,但渲染结果是一个空图像。主线程和新线程中rtb的实例是否相同?

编辑: 这是我最近的尝试。

我在 MouseUp 事件上创建线程:

var curLayer = GetItemsPanel((canvasDataBinding.ItemContainerGenerator.ContainerFromIndex(Binding_LayersListView.SelectedIndex)));
Thread RASTERIZE_THREAD = new Thread(() => { exportToImage(curLayer.ActualWidth, curLayer.ActualHeight, curLayer); });
RASTERIZE_THREAD.Start();

这是我新线程使用的方法:

void exportToImage(double width, double height, Visual visual)
{
    var filename = string.Format(@"{0}.png", Guid.NewGuid());
    var tempFile = Path.Combine(tempDir, filename);
    Rect rect = new Rect(0, 0, width, height);
    RenderTargetBitmap rtb = new RenderTargetBitmap((int)rect.Right,
        (int)rect.Bottom, 96d, 96d, System.Windows.Media.PixelFormats.Default);

    this.Dispatcher.Invoke(new Action(() =>
    {
        rtb.Render(visual);
    }));

    //endcode as PNG
    BitmapEncoder pngEncoder = new PngBitmapEncoder();
    pngEncoder.Frames.Add(BitmapFrame.Create(rtb));

    //save to memory stream
    System.IO.MemoryStream ms = new System.IO.MemoryStream();

    pngEncoder.Save(ms);
    ms.Close();
    System.IO.File.WriteAllBytes(tempFile, ms.ToArray());
}

那么为什么它告诉我调用线程无法访问这个对象,因为另一个线程拥有它 rtb.Render(visual)?我没有从其他任何地方调用 exportToImage,那么为什么 Dispatcher 与我创建的线程没有关联?

编辑: 我需要在 Dispatcher.Invoke() 中创建 RebderTargetBitmap。

【问题讨论】:

  • 两个问题!在线程中使用调度程序有效地使线程无用,其次不能保证线程在使用结果之前完成BitmapFrame.Create(rtb)
  • 我想我明白了。那么我应该在 exportToImage 方法上创建一个新线程,然后在方法内部使用调度程序吗?
  • 渲染时间是否太长?如果不是,则线程只是开销,如果是,则值得投资。其次,该方法是同步的,因为它需要一个渲染文件才能返回,因此使渲染函数异步将无济于事,因为该方法必须等到渲染完成。在这种情况下,该方法应该返回 void。
  • @pushpraj 是的,当我要渲染的画布上填充了太多图像并且画布的尺寸增加时,它需要很长时间。因此,为了使其异步,也许我可以将渲染图像的路径存储到某个共享缓冲区,然后在主线程中定期检查缓冲区。

标签: c# wpf multithreading wpf-controls dispatcher


【解决方案1】:

这里有一段代码,你可以如何在线程中渲染它并使其异步

整个想法是

  • 将视觉对象序列化为 xaml 字符串
  • 创建一个 STA 线程
  • 然后让线程反序列化相同。
  • 在内容控件中托管反序列化的视觉对象,并执行 Arrange 等操作。
  • 最后但同样重要的是,渲染它(取自您的代码)

此代码不依赖于 Dispatcher,因此它能够完全异步运行

    void exportToImage(double width, double height, Visual visual)
    {
        //this line can not be inside a thread because different thread owns the visual
        string visualData = XamlWriter.Save(visual);

        Thread t = new Thread(() =>
            {
                var filename = string.Format(@"{0}.png", Guid.NewGuid());
                var tempFile = System.IO.Path.Combine("c:\\", filename);

                Rect rect = new Rect(0, 0, width, height);

                //create a host control to host the visual
                ContentControl cc = new ContentControl();
                cc.Content = XamlReader.Parse(visualData);
                //call Arrange to let control perform layout (important)
                cc.Arrange(rect);

                RenderTargetBitmap rtb = new RenderTargetBitmap((int)rect.Right,
                    (int)rect.Bottom, 96d, 96d, System.Windows.Media.PixelFormats.Default);

                rtb.Render(cc);

                //endcode as PNG
                BitmapEncoder pngEncoder = new PngBitmapEncoder();
                pngEncoder.Frames.Add(BitmapFrame.Create(rtb));

                //save to memory stream
                System.IO.MemoryStream ms = new System.IO.MemoryStream();

                pngEncoder.Save(ms);
                ms.Close();
                System.IO.File.WriteAllBytes(tempFile, ms.ToArray());
            });
        //STA is necessary for XamlReader.Parse, and proper rendering
        t.SetApartmentState(ApartmentState.STA);
        t.Start();
    }

我已成功测试此代码以呈现一些测试视觉效果。但结果可能会因您的使用方式而异。作为猜测绑定和光栅图像可能有一些问题,但在我的测试中它就像一个魅力。

【讨论】:

  • 我让它在我的一些视觉效果上工作,但我不能让它在有多个孩子的画布上工作(图像控件)。我读了这篇文章social.msdn.microsoft.com/forums/windowsapps/en-us/…,但我认为我无法保存我的画布。有什么想法吗?
  • 对于画布,您可能需要在画布上显式设置高度和宽度,因为画布的默认高度宽度为 0。您是否可以分享您的代码的工作示例,我可以给一个试试看。
  • 我尽量简化我的代码,这里是应用程序。 www68.zippyshare.com/v/88730897/file.html你只需要看看MainWindow.xaml和Xenoblend.cs
  • 在 2048x2048 对我来说相当快,但是当我将其设为 10000x10000 时会看到一些滞后。也许是由于处理器差异。其次,代码使用 dispatcher.invoke 来渲染,所以它不是异步的。答案中建议的画布序列化不起作用,因为画布项目是数据绑定的。此时作为一个选项,您可以从主层删除数据绑定并直接推送图像,然后序列化将起作用并使其异步。或者,您可以尝试在具有较低优先级的调度程序上使用 BeginInvoke see this
  • @Infodayne,我确实尝试重新编写您的代码以使其异步,下载TextureGenerator_WPF.zip 看看这是否有帮助。要更改纹理大小,请使用窗口资源中的CanvasSize double 值。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-16
  • 1970-01-01
  • 1970-01-01
  • 2016-01-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多