C# 循环中的异步操作-如何保持对执行的控制?
接着是问题 我正在尝试生成并保存一系列图像。渲染由完成,我听说它使用WPF复合渲染线程。这会导致问题,因为它是异步执行的 我最初的问题是,我无法保存给定的图像,因为当时它还没有渲染,我正试图保存它。上面的答案提供了一种解决方法,将“保存”操作放在以低优先级调用的C# 循环中的异步操作-如何保持对执行的控制?,c#,wpf,multithreading,asynchronous,helix-3d-toolkit,C#,Wpf,Multithreading,Asynchronous,Helix 3d Toolkit,接着是问题 我正在尝试生成并保存一系列图像。渲染由完成,我听说它使用WPF复合渲染线程。这会导致问题,因为它是异步执行的 我最初的问题是,我无法保存给定的图像,因为当时它还没有渲染,我正试图保存它。上面的答案提供了一种解决方法,将“保存”操作放在以低优先级调用的操作中,从而确保渲染首先完成 这对于一个图像来说很好,但在我的应用程序中,我需要多个图像。目前,我无法控制事件的顺序,因为它们是异步发生的。我正在使用For循环,无论渲染和保存图像的进度如何,循环都会继续。我需要一个接一个地生成图像,在开
操作中,从而确保渲染首先完成
这对于一个图像来说很好,但在我的应用程序中,我需要多个图像。目前,我无法控制事件的顺序,因为它们是异步发生的。我正在使用For
循环,无论渲染和保存图像的进度如何,循环都会继续。我需要一个接一个地生成图像,在开始下一个之前有足够的时间进行渲染和保存
我尝试过在循环中加入延迟,但这会导致它自身的问题。例如,代码中注释的async wait
会导致跨线程问题,因为数据是在执行渲染的不同线程上创建的。我尝试了一个简单的延迟,但那只是锁定了一切——我想部分原因是我正在等待的save操作的优先级很低
我不能简单地将其视为一批独立的无关异步任务,因为我在GUI中使用单个HelixViewport3D
控件。图像必须按顺序生成
我确实尝试过一种递归方法,SaveHelixPlotAsBitmap()
调用DrawStuff()
,但效果不太好,而且似乎不是一种好方法
我尝试在每个循环上设置一个标志('busy'),并等待它在继续之前被重置,但这仍然不起作用,因为异步执行。类似地,我尝试使用计数器使循环与生成的图像数量保持一致,但遇到了类似的问题
我似乎陷入了一个线程和异步操作的兔子洞,我不想陷入其中
我如何解决这个问题
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);
}
}
class-Foo{
公共名单点;
公共色彩;
public Foo(Color col){//构造函数创建三个任意三维点
points=new List(){new Point3D(0,0,0),new Point3D(1,0,0),new Point3D(0,0,1)};
点颜色=颜色;
}
}
公共部分类主窗口:窗口
{
int i=-1;//计数器
公共主窗口()
{
初始化组件();
}
private void Go_Click(对象发送方,RoutedEventArgs e)//起点
{
//创建每个具有三个三维点的对象列表。。。
列表栏=newlist(){newfoo(Colors.Red)、newfoo(Colors.Green)、newfoo(Colors.Blue)};
foreach(酒吧里的食物)
{
i++;
DrawStuff(b,SaveHelixPlotAsBitmap);//打印到helixViewport3D控件(“点”=3D点列表)
//第一次这样很好,但是由于渲染和图像捕获
//是异步的。我需要保持它的顺序,即。
//渲染图像1->保存图像1
//渲染图像2->保存图像2
//等等。
}
}
私有void DrawStuff(Foo thisFoo,动作渲染完成)
{
//等待System.Threading.Tasks.Task.Run(()=>
//{
Point3DCollection数据列表=新的Point3DCollection();
PointsVisual3D cloudPoints=newpointsvisual3d{Color=thisFoo.PointColor,Size=5.0f};
foreach(此foo.points中的点3D p)
{
添加(p);
}
cloudPoints.Points=数据列表;
//向helixPlot添加几何体。它在WPF复合渲染线程中异步渲染。。。
helixViewport3D.Children.Add(cloudPoints);
helixViewport3D.CameraController.ZoomExtents();
//保存图像(低优先级意味着首先完成渲染,这很关键)。。
Dispatcher.BeginInvoke(渲染完成,DispatcherPriority.ContextIdle);
//});
}
私有void SaveHelixPlotAsBitmap()
{
Viewport3DHelper.SaveBitmap(helixViewport3D.Viewport,$@“E:\test{i}.png”,null,4,BitmapExporter.OutputFormat.png);
}
}
注意这些示例只是为了证明一个概念,TaskCompletionSource需要处理错误
鉴于此测试窗口
下面是一个如何使用事件来了解视图何时处于所需状态的示例
使用System.Threading.Tasks;
使用System.Windows;
使用System.Windows.Controls;
命名空间WpfApp2
{
公共部分类主窗口:窗口
{
公共主窗口()
{
初始化组件();
DoWorkAsync();
}
专用异步任务DoWorkAsync()
{
对于(int i=0;i<10;i++)
{
等待RenderAndCapture();
}
}
私有异步任务RenderAndCapture()
{
等待RenderAsync();
CaptureScreen();
}
私有任务RenderAsync()
{
var taskCompletionSource=新的taskCompletionSource();
Dispatcher.Invoke(()=>
{
var panel=newtextblock{Text=“NewBlock”};
panel.Loaded+=onpanel onload;
StackPanel.Children.Add(面板);
无效OnPanelOnLoaded(对象发送方,RoutedEventArgs参数)
{
面板已加载-=面板已加载;
taskCompletionSource.TrySetResult(null);
}
});
返回taskCompletionSource.Task;
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Go_Click(object sender, RoutedEventArgs e) // STARTING POINT
{
DoWorkAsync();
}
private async Task DoWorkAsync()
{
// 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) };
int i = -1; // init counter
foreach (Foo b in bar)
{
i++;
await RenderAndCapture(b, i);
}
}
private async Task RenderAndCapture(Foo b, int i)
{
await RenderAsync(b);
SaveHelixPlotAsBitmap(i);
}
private Task RenderAsync(Foo b)
{
var taskCompletionSource = new TaskCompletionSource<object>();
Dispatcher.Invoke(() =>
{
DrawStuff(b);
Dispatcher.BeginInvoke(new Action(() =>
{
taskCompletionSource.TrySetResult(null);
}), DispatcherPriority.ContextIdle);
});
return taskCompletionSource.Task;
}
private void DrawStuff(Foo thisFoo)
{
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...
helixPlot.Children.Add(cloudPoints);
helixPlot.CameraController.ZoomExtents();
}
private void SaveHelixPlotAsBitmap(int i) // screenshot
{
Viewport3DHelper.SaveBitmap(helixPlot.Viewport, $@"E:\test{i}.png", null, 4, BitmapExporter.OutputFormat.Png);
}
}