WPF 绑定 IsAsync 失败
拥有一个大多数属性都非常轻量级的对象 - 最多 200 个字符的文本。 FlowDocument 是一个属性,它可能很大并且需要异步检索它。当我设置 Async = True 并显示以下消息时,它会失败:“调用线程无法访问此对象,因为另一个线程拥有它。”
<FlowDocumentReader Name="FlowDocumentPageViewer1" HorizontalAlignment="Stretch"
Document="{Binding Source={x:Static Application.Current}, Path=MyGabeLib.Search.SelectedDoc.DocFlowDocument, Mode=OneWay, IsAsync=True}" />
Production Get 更复杂,但即使使用简单的硬编码 FlowDocument,IsAsyc True 也会出现同样的失败。
public FlowDocument DocFlowDocument
{
get
{
FlowDocument docFlowDocumentFast = new FlowDocument();
Paragraph p = new Paragraph();
Run r = new Run();
r.Foreground = System.Windows.Media.Brushes.Red;
r.Text = "Hard Code Simple FlowDocument";
p.Inlines.Add(r);
docFlowDocumentFast.Blocks.Add(p);
return docFlowDocumentFast;
}
{
它确实调用了 SelectedDoc.DocFlowDocument 并且返回了文档! 当 IsAsync = False 时,它运行得很好。 我认为问题出在静态源上,但显然我不知道如何解决它。
public partial class App : Application
{
private static GabeLib staticGabeLib = new GabeLib();
private GabeLib myGabeLib = staticGabeLib;
public GabeLib MyGabeLib
{ get { return myGabeLib; } }
public static GabeLib StaticGabeLib
{ get { return staticGabeLib; } }
}
当 GabeLib 启动时,它会从数据库读取应用程序和用户设置。
如果有更好的方法来解决这个问题,我会尝试一下。由于 FlowDocument 可以为 3 mb,而所有其他属性加起来为 10K,这对性能造成很大影响,并且最常用的按钮是下一个对象。 FlowDocument 来自 SQL 中的 varchar(max),并采用换行符和突出显示的单词进行格式化。它不仅很大,与其他房产相比,它也很昂贵。
FlowDocumentReader 本身似乎有一些异步支持,因为在大型文档上我可以快速打开第一页,然后页面加载速度约为 100/秒。但我仍然希望它在检索所有其他属性后获取第 1 页。
问题正如 martin 所说“由于 FlowDocument 是一个调度程序对象,因此只能从创建它的线程访问它”。
解决方案是序列化为 XAML 字符串。
public string XAMLdocFlowDocument
{
get
{
Thread.Sleep(6000);
return XamlWriter.Save(FlowDocumentSlow);
}
}
器绑定到字符串 Converter
<FlowDocumentReader Grid.Row="3" Grid.Column="0" VerticalAlignment="Stretch"
Document="{Binding Path=XAMLdocFlowDocument, IsAsync=True,
Converter={StaticResource flowDocumentToXamlConverter}, Mode=OneWay}" />
使用转换
[ValueConversion(typeof(string), typeof(FlowDocument))]
public class FlowDocumentToXamlConverter : IValueConverter
{
#region IValueConverter Members
/// <summary>
/// Converts from XAML markup to a WPF FlowDocument.
/// </summary>
public object Convert(object value, System.Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
var flowDocument = new FlowDocument();
if (value != null)
{
var xamlText = (string)value;
flowDocument = (FlowDocument)XamlReader.Parse(xamlText);
}
// Set return value
return flowDocument;
}
Have an object with most of properties very lightweight - text up to 200 character. One property is FlowDocument that can be large and want to retrieve it Async. It fails when I set Async = True with the message: "The calling thread cannot access this object because a different thread owns it."
<FlowDocumentReader Name="FlowDocumentPageViewer1" HorizontalAlignment="Stretch"
Document="{Binding Source={x:Static Application.Current}, Path=MyGabeLib.Search.SelectedDoc.DocFlowDocument, Mode=OneWay, IsAsync=True}" />
Production Get is more complex but the same failure on IsAsyc True even with a simple hard coded FlowDocument.
public FlowDocument DocFlowDocument
{
get
{
FlowDocument docFlowDocumentFast = new FlowDocument();
Paragraph p = new Paragraph();
Run r = new Run();
r.Foreground = System.Windows.Media.Brushes.Red;
r.Text = "Hard Code Simple FlowDocument";
p.Inlines.Add(r);
docFlowDocumentFast.Blocks.Add(p);
return docFlowDocumentFast;
}
{
It does call SelectedDoc.DocFlowDocument and the document is returned!
With IsAsync = False it runs just fine.
I think the problem is the Static Source but clearly I don't know how to fix it.
public partial class App : Application
{
private static GabeLib staticGabeLib = new GabeLib();
private GabeLib myGabeLib = staticGabeLib;
public GabeLib MyGabeLib
{ get { return myGabeLib; } }
public static GabeLib StaticGabeLib
{ get { return staticGabeLib; } }
}
When GabeLib starts it reads in application and user setting from a database.
If there is a better way to approach this I will give it a try. Since the FlowDocument can be 3 mb and all the other properties 10K combined this is big performance hit and the most used button is next object. The FlowDocument comes from from a varchar(max) in SQL and gets formatted with line breaks and words highlighted. It is not just big - compared to the other properties it is also expensive.
The FlowDocumentReader itself appears to have some async support as on a large document I get the first page fast and then pages load at about 100/second. But I would still like it to get page 1 after all the other properties are retrieved.
The problem was as martin stated "Since a FlowDocument is a dispatcher object it can only be accessed from the thread that created it".
The solution was to serialize to a XAML string.
public string XAMLdocFlowDocument
{
get
{
Thread.Sleep(6000);
return XamlWriter.Save(FlowDocumentSlow);
}
}
Bind to the string with a converter
<FlowDocumentReader Grid.Row="3" Grid.Column="0" VerticalAlignment="Stretch"
Document="{Binding Path=XAMLdocFlowDocument, IsAsync=True,
Converter={StaticResource flowDocumentToXamlConverter}, Mode=OneWay}" />
Converter
[ValueConversion(typeof(string), typeof(FlowDocument))]
public class FlowDocumentToXamlConverter : IValueConverter
{
#region IValueConverter Members
/// <summary>
/// Converts from XAML markup to a WPF FlowDocument.
/// </summary>
public object Convert(object value, System.Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
var flowDocument = new FlowDocument();
if (value != null)
{
var xamlText = (string)value;
flowDocument = (FlowDocument)XamlReader.Parse(xamlText);
}
// Set return value
return flowDocument;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
如果没有看到代码,我猜测 FlowDocument 是在读取属性时创建(并加载)的。由于属性绑定是异步的,因此这是在后台线程中完成的。
由于 FlowDocument 是一个调度程序对象,因此只能从创建它的线程访问它,在本例中,该线程是读取属性的后台线程。
因此,您的 UI 线程无法访问创建的 FlowDocument。
您将需要另一种异步加载文档的方法。
也许您可以使用同步(正常)绑定并使用 XamlReader.LoadAsync 来加载文档?我自己没有尝试过,但我想它值得尝试。
Without seeing the code I'm guessing that the FlowDocument is created (and loaded) when the property is read. This is done in a background thread since the property binding is async.
Since a FlowDocument is a dispatcher object it can only be accessed from the thread that created it, which in this case is the background thread that read the property.
So, the created FlowDocument cannot be accessed by your UI-thread.
You'll need another approach for loading the document asynchronously.
Perhaps you could use synchronous (normal) binding and use XamlReader.LoadAsync to load the document? I haven't tried it myself but I'm guessing it's worth experimenting with.