【发布时间】:2020-08-16 01:53:50
【问题描述】:
向this one提出后续问题。
我正在尝试生成并保存一系列图像。渲染由Helix Toolkit 完成,我听说它使用了 WPF 复合渲染线程。这会导致问题,因为它是异步执行的。
我最初的问题是我无法保存给定的图像,因为当时我试图保存它还没有被渲染。上面的答案通过将“保存”操作放在以低优先级调用的Action 中来解决此问题,从而确保渲染首先完成。
这对于一张图片来说很好,但在我的应用程序中我需要多张图片。就目前而言,我无法控制事件的顺序,因为它们是异步发生的。我正在使用For 循环,无论渲染和保存图像的进度如何,它都会继续。我需要一张一张生成图片,在开始下一张之前有足够的时间进行渲染和保存。
我曾尝试在循环中设置延迟,但这会导致其自身的问题。例如,代码中注释的async await 会导致跨线程问题,因为数据是在与进行渲染的线程不同的线程上创建的。我尝试了一个简单的延迟,但这只会锁定所有内容 - 我认为部分原因是我正在等待的保存操作的优先级非常低。
我不能简单地将其视为一批独立的不相关的异步任务,因为我在 GUI 中使用单个 HelixViewport3D 控件。图像必须按顺序生成。
我确实尝试了一种递归方法,其中SaveHelixPlotAsBitmap() 调用DrawStuff(),但效果不是很好,而且似乎不是一个好方法。
我尝试在每个循环上设置一个标志(“忙”)并等待它被重置,然后再继续,但这不起作用 - 再次,因为异步执行。同样,我尝试使用计数器使循环与已生成但遇到类似问题的图像数量保持同步。
我似乎陷入了一个我不想陷入的线程和异步操作的兔子洞。
我该如何解决这个问题?
class Foo {
public List<Point3D> points;
public Color PointColor;
public Foo(Color col) { // constructor creates three arbitrary 3D points
points = new List<Point3D>() { new Point3D(0, 0, 0), new Point3D(1, 0, 0), new Point3D(0, 0, 1) };
PointColor = col;
}
}
public partial class MainWindow : Window
{
int i = -1; // counter
public MainWindow()
{
InitializeComponent();
}
private void Go_Click(object sender, RoutedEventArgs e) // STARTING POINT
{
// Create list of objects each with three 3D points...
List<Foo> bar = new List<Foo>(){ new Foo(Colors.Red), new Foo(Colors.Green), new Foo(Colors.Blue) };
foreach (Foo b in bar)
{
i++;
DrawStuff(b, SaveHelixPlotAsBitmap); // plot to helixViewport3D control ('points' = list of 3D points)
// This is fine the first time but then it runs away with itself because the rendering and image grabbing
// are asynchronous. I need to keep it sequential i.e.
// Render image 1 -> save image 1
// Render image 2 -> save image 2
// Etc.
}
}
private void DrawStuff(Foo thisFoo, Action renderingCompleted)
{
//await System.Threading.Tasks.Task.Run(() =>
//{
Point3DCollection dataList = new Point3DCollection();
PointsVisual3D cloudPoints = new PointsVisual3D { Color = thisFoo.PointColor, Size = 5.0f };
foreach (Point3D p in thisFoo.points)
{
dataList.Add(p);
}
cloudPoints.Points = dataList;
// Add geometry to helixPlot. It renders asynchronously in the WPF composite render thread...
helixViewport3D.Children.Add(cloudPoints);
helixViewport3D.CameraController.ZoomExtents();
// Save image (low priority means rendering finishes first, which is critical)..
Dispatcher.BeginInvoke(renderingCompleted, DispatcherPriority.ContextIdle);
//});
}
private void SaveHelixPlotAsBitmap()
{
Viewport3DHelper.SaveBitmap(helixViewport3D.Viewport, $@"E:\test{i}.png", null, 4, BitmapExporter.OutputFormat.Png);
}
}
【问题讨论】:
-
Asynchronous Programming - 仔细阅读。除了实现
IProgress接口的类Progress,同步回调在并发执行的情况下可能会有所帮助。避免直接使用Dispatcher,这会使代码变得怪异。 -
还可以考虑this
CancellationToken用法示例。 -
我刚刚查看了源代码,它看起来像
PointsVisual3D派生自FrameworkElement,因此您可以收听Loaded事件以了解呈现了单个点。您可以将这些事件组合成一个表示渲染完成的事件。我猜想通过任务队列将渲染和屏幕截图调用作为单独的任务分派给 ui 线程,然后像任务完成源一样异步等待上一个工作单元,来实现阻塞行为以便您可以截图是最容易的。
标签: c# wpf multithreading asynchronous helix-3d-toolkit