循环内的异步操作-如何保持执行控制权?

如何解决循环内的异步操作-如何保持执行控制权?

关注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),new Point3D(1,new Point3D(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);
    }
}

解决方法

注意这些示例只是为了证明一个概念,在TaskCompletionSource上需要进行一些工作来处理错误

给出此测试窗口

<Window x:Class="WpfApp2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <StackPanel x:Name="StackPanel"/>
    </Grid>
</Window>

这里是一个示例,该示例介绍了如何使用事件来知道视图何时处于所需状态。

using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;

namespace WpfApp2
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DoWorkAsync();
        }

        private async Task DoWorkAsync()
        {
            for (int i = 0; i < 10; i++)
            {
                await RenderAndCapture();
            }
        }

        private async Task RenderAndCapture()
        {
            await RenderAsync();
            CaptureScreen();
        }

        private Task RenderAsync()
        {
            var taskCompletionSource = new TaskCompletionSource<object>();
            Dispatcher.Invoke(() =>
            {
                var panel = new TextBlock {Text = "NewBlock"};
                panel.Loaded += OnPanelOnLoaded;

                StackPanel.Children.Add(panel);

                void OnPanelOnLoaded(object sender,RoutedEventArgs args)
                {
                    panel.Loaded -= OnPanelOnLoaded;
                    taskCompletionSource.TrySetResult(null);
                }
            });

            return taskCompletionSource.Task;
        }

        private void CaptureScreen()
        {
            // Capture Image
        }
    }
}

如果要从外部调用sync方法,则可以实现任务队列。

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;

namespace WpfApp2
{
    public class TaskQueue
    {
        private readonly SemaphoreSlim _semaphore;
        public TaskQueue()
        {
            _semaphore = new SemaphoreSlim(1);
        }

        public async Task Enqueue(Func<Task> taskFactory)
        {
            await _semaphore.WaitAsync();
            try
            {
                await taskFactory();
            }
            finally
            {
                _semaphore.Release();
            }
        }
    }

    public partial class MainWindow : Window
    {
        private readonly TaskQueue _taskQueue;

        public MainWindow()
        {
            _taskQueue = new TaskQueue();
            InitializeComponent();
            DoWork();
        }

        private void DoWork()
        {
            for (int i = 0; i < 10; i++)
            {
                QueueRenderAndCapture();
            }
        }

        private void QueueRenderAndCapture()
        {
            _taskQueue.Enqueue(() => RenderAndCapture());
        }

        private async Task RenderAndCapture()
        {
            await RenderAsync();
            CaptureScreen();
        }

        private Task RenderAsync()
        {
            var taskCompletionSource = new TaskCompletionSource<object>();
            Dispatcher.Invoke(() =>
            {
                var panel = new TextBlock {Text = "NewBlock"};
                panel.Loaded += OnPanelOnLoaded;

                StackPanel.Children.Add(panel);

                void OnPanelOnLoaded(object sender,RoutedEventArgs args)
                {
                    panel.Loaded -= OnPanelOnLoaded;
                    taskCompletionSource.TrySetResult(null);
                }
            });

            return taskCompletionSource.Task;
        }

        private void CaptureScreen()
        {
            // Capture Screenshot
        }
    }
}

这将确保UI处于每次迭代所需的状态 Render in stages

您当然需要扩展它,以便侦听要渲染的每个点的Loaded事件。

编辑: 由于PointsVisual3D没有Loaded事件,因此可以通过挂接以前使用的事件来完成任务。不理想,但是应该可以。

private Task RenderAsync()
{
    var taskCompletionSource = new TaskCompletionSource<object>();
    Dispatcher.Invoke(() =>
    {
        var panel = new TextBlock {Text = "NewBlock"};

        StackPanel.Children.Add(panel);

        Dispatcher.BeginInvoke(new Action(() =>
        {
            taskCompletionSource.TrySetResult(null);
        }),DispatcherPriority.ContextIdle);
    });

    return taskCompletionSource.Task;
}
,

下面的解决方案。这是我对Jason答案中提供的代码的实现。所有这些都归功于杰森的重要职责。

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

    }

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-