如何解决循环内的异步操作-如何保持执行控制权?
关注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
}
}
}
您当然需要扩展它,以便侦听要渲染的每个点的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 举报,一经查实,本站将立刻删除。