Ben*_*sen 1 c# parallel-processing wpf xaml task-parallel-library
我们有一个WPF应用程序,它有一个带有缓存的VirtualizingStackPanel的ListBox.不是因为它有大量的元素(通常小于20,但在极端情况下可能高达100或更多),但因为元素需要时间来生成.这些元素实际上是UIElement对象.因此应用程序动态地需要生成UIElements.
问题在于,即使虚拟化似乎有效,但应用程序仍然很难做出响应,这是一个概念验证解决方案,具有最小的"噪音".
所以我们认为,由于主要问题是我们动态生成复杂的UIElement对象,我们需要并行执行,即离线.但是我们得到一个代码需要在STA线程上运行的错误:
调用线程必须是STA,因为许多UI组件都需要这个.
这是否意味着我们无法在WPF主UI线程以外的线程上生成UI(UIElement对象)?
以下是我们的概念验证解决方案中的相关代码片段:
public class Person : ObservableBase
{
// ...
UIElement _UI;
public UIElement UI
{
get
{
if (_UI == null)
{
ParallelGenerateUI();
}
return _UI;
}
}
private void ParallelGenerateUI()
{
var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() => GenerateUI())
.ContinueWith(t =>
{
_UI = t.Result;
RaisePropertyChanged("UI");
}, scheduler);
}
private UIElement GenerateUI()
{
var tb = new TextBlock();
tb.Width = 800.0;
tb.TextWrapping = TextWrapping.Wrap;
var n = rnd.Next(10, 5000);
for (int i = 0; i < n; i++)
{
tb.Inlines.Add(new Run("A line of text. "));
}
return tb;
}
// ...
}
Run Code Online (Sandbox Code Playgroud)
这是一个相关的XAML:
<DataTemplate x:Key="PersonDataTemplate" DataType="{x:Type local:Person}">
<Grid>
<Border Margin="4" BorderBrush="Black" BorderThickness="1" MinHeight="40" CornerRadius="3" Padding="3">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<!--<RowDefinition />-->
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="Name : " Grid.Row="0" FontWeight="Bold" HorizontalAlignment="Right" />
<TextBlock Grid.Column="1" Grid.Row="0" Text="{Binding Name}" />
<TextBlock Text=" - Age : " Grid.Column="2" Grid.Row="0" FontWeight="Bold"
HorizontalAlignment="Right" />
<TextBlock Grid.Column="3" Grid.Row="0" Text="{Binding Age}" />
<ContentControl Grid.Column="4" Grid.Row="0" Content="{Binding Path=UI}" />
</Grid>
</Border>
</Grid>
</DataTemplate>
Run Code Online (Sandbox Code Playgroud)
如您所见,我们将数据绑定到UIElement类型的属性UI.
<ListBox x:Name="listbox" ItemsSource="{Binding Persons}" Background="LightBlue"
ItemTemplate="{StaticResource PersonDataTemplate}"
ItemContainerStyle="{StaticResource ListBoxItemStyle}"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.IsVirtualizingWhenGrouping="True"
VirtualizingStackPanel.ScrollUnit="Pixel"
VirtualizingStackPanel.CacheLength="10,10"
VirtualizingStackPanel.CacheLengthUnit="Item"
>
<ListBox.GroupStyle>
<GroupStyle HeaderTemplate="{StaticResource GroupHeaderTemplate}" />
</ListBox.GroupStyle>
</ListBox>
Run Code Online (Sandbox Code Playgroud)
在结束语中,我们的应用程序所做的是创建一个代码视图,其中列表是程序,它再次包含结构化内容的混合(一方面是参数和局部变量,另一方面是语句和表达式).
换句话说,我们的UIElement对象太复杂了,无法单独通过数据绑定创建.
我们的另一个想法是在XAML中使用"异步"设置,因为它似乎可以创建"非阻塞UI"但我们无法实现这一点,因为我们得到与上面相同的错误:
调用线程必须是STA,因为许多UI组件都需要这个.
堆栈跟踪:
System.InvalidOperationException was unhandled by user code
HResult=-2146233079
Message=The calling thread must be STA, because many UI components require this.
Source=PresentationCore
StackTrace:
at System.Windows.Input.InputManager..ctor()
at System.Windows.Input.InputManager.GetCurrentInputManagerImpl()
at System.Windows.Input.KeyboardNavigation..ctor()
at System.Windows.FrameworkElement.FrameworkServices..ctor()
at System.Windows.FrameworkElement.EnsureFrameworkServices()
at System.Windows.FrameworkElement..ctor()
at System.Windows.Controls.TextBlock..ctor()
at WPF4._5_VirtualizingStackPanelNewFeatures.Person.GenerateUI() in c:\Users\Christian\Desktop\WPF4.5_VirtualizingStackPanelNewFeatures\WPF4.5_VirtualizingStackPanelNewFeatures\Person.cs:line 84
at WPF4._5_VirtualizingStackPanelNewFeatures.Person.<ParallelGenerateUI>b__2() in c:\Users\Christian\Desktop\WPF4.5_VirtualizingStackPanelNewFeatures\WPF4.5_VirtualizingStackPanelNewFeatures\Person.cs:line 68
at System.Threading.Tasks.Task`1.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
InnerException:
Run Code Online (Sandbox Code Playgroud)
编辑:
1)添加了更多XAML.2)添加了堆栈跟踪.
Pat*_*ick 14
我在正常的c#环境中遇到了同样的问题.我也尝试了很多东西.您是否计算了控件的大小以提前调整父级的大小?不幸的是,我这样做了.
您还可以创建一个动态嵌套子项的控件.通过这种方式,您可以创建一种UIElement适配器.适配器在开始时创建,并具有创建UIElements的所有信息.适配器可以及时按需在STA线程上创建请求的子节点.向上或向下滚动时,您可以在滚动方向上提前创建子项.这样你可以从例如5-10个UI元素开始,然后通过向上滚动来计算.
我知道这不是那么好,如果框架内有一些技术提供类似的东西会更好,但我还没有找到它.
你也可以看看这两件事.一个人帮助我控制响应.另一个仍然是开放的,因为您需要.NET Framework 4.5:
SuspendLayout而且ResumeLayout操作不是很好.你可以试试这个:
/// <summary>
/// An application sends the WM_SETREDRAW message to a window to allow changes in that
/// window to be redrawn or to prevent changes in that window from being redrawn.
/// </summary>
private const int WM_SETREDRAW = 11;
/// <summary>
/// Suspends painting for the target control. Do NOT forget to call EndControlUpdate!!!
/// </summary>
/// <param name="control">visual control</param>
public static void BeginControlUpdate(Control control)
{
Message msgSuspendUpdate = Message.Create(control.Handle, WM_SETREDRAW, IntPtr.Zero,
IntPtr.Zero);
NativeWindow window = NativeWindow.FromHandle(control.Handle);
window.DefWndProc(ref msgSuspendUpdate);
}
/// <summary>
/// Resumes painting for the target control. Intended to be called following a call to BeginControlUpdate()
/// </summary>
/// <param name="control">visual control</param>
public static void EndControlUpdate(Control control)
{
// Create a C "true" boolean as an IntPtr
IntPtr wparam = new IntPtr(1);
Message msgResumeUpdate = Message.Create(control.Handle, WM_SETREDRAW, wparam,
IntPtr.Zero);
NativeWindow window = NativeWindow.FromHandle(control.Handle);
window.DefWndProc(ref msgResumeUpdate);
control.Invalidate();
control.Refresh();
}
Run Code Online (Sandbox Code Playgroud)Dispatcher.Yield