C# 循环中的异步操作-如何保持对执行的控制?

C# 循环中的异步操作-如何保持对执行的控制?,c#,wpf,multithreading,asynchronous,helix-3d-toolkit,C#,Wpf,Multithreading,Asynchronous,Helix 3d Toolkit,接着是问题 我正在尝试生成并保存一系列图像。渲染由完成,我听说它使用WPF复合渲染线程。这会导致问题,因为它是异步执行的 我最初的问题是,我无法保存给定的图像,因为当时它还没有渲染,我正试图保存它。上面的答案提供了一种解决方法,将“保存”操作放在以低优先级调用的操作中,从而确保渲染首先完成 这对于一个图像来说很好,但在我的应用程序中,我需要多个图像。目前,我无法控制事件的顺序,因为它们是异步发生的。我正在使用For循环,无论渲染和保存图像的进度如何,循环都会继续。我需要一个接一个地生成图像,在开

接着是问题

我正在尝试生成并保存一系列图像。渲染由完成,我听说它使用WPF复合渲染线程。这会导致问题,因为它是异步执行的

我最初的问题是,我无法保存给定的图像,因为当时它还没有渲染,我正试图保存它。上面的答案提供了一种解决方法,将“保存”操作放在以低优先级调用的
操作中,从而确保渲染首先完成

这对于一个图像来说很好,但在我的应用程序中,我需要多个图像。目前,我无法控制事件的顺序,因为它们是异步发生的。我正在使用
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);
        }

    }