我为DocumentViewer生成了一份文档。它很慢,所以我想释放UI线程。使用async / await我得到一个例外,"调用线程必须是STA"。我相信我需要对UI线程传递/返回的值进行编组,但我似乎无法使其工作。我已经以各种方式尝试过Dispatcher.Invoke。
任何人都知道如何使用async / await做到这一点?
这是一个简洁实用的示例,您可以将其粘贴到一个全新的WPF项目中(WpfApp1):
<Window x:Class="WpfApp1.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"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<DocumentViewer Document="{Binding Document}"/>
</Window>
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
RebuildDocument(); // Called various places
}
public double Length { get; set; } = 100;
FixedDocument document;
public FixedDocument Document
{
get { return document; }
set { if (document == value) return; document = value; OnPropertyChanged(); }
}
async void RebuildDocument()
{
Document = await GenerateDocument(Length);
}
private static async Task<FixedDocument> GenerateDocument(double length)
{
return await Task.Run(() =>
{
// Dummy work
return new FixedDocument() {
Pages = { new PageContent() { Child = new FixedPage() {
Width = length, Height = length,
Children = { new TextBlock() { Text = "dummy page" }}}}}};
});
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string propertyName = null)
{ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
}
答案 0 :(得分:0)
我至少可以想到两种方法:
在UI线程的后台任务中构建你的FixedDocument
,同时在最里面的循环上广泛产生(并观察取消):
private static async Task<FixedDocument> GenerateDocument(double length, CancellationToken token = default(CancellationToken))
{
var doc = new FixedDocument();
while (!complete)
{
token.ThrowIfCancellationRequested();
// ...
doc.Children.Add(anotherChild)
// ...
// yield to process user input as often as possible
await System.Windows.Threading.Dispatcher.Yield(System.Windows.Threading.DispatcherPriority.Input);
}
}
在新的工作者WPF线程上构建它(因为FixedDocument
是DispatcherObject
并且需要Dispatcher
循环),在那里将其序列化为XAML,然后将其反序列化为原始UI线程上的FixedDocument
的新实例。我不确定直接反序列化是否足够快,不会阻止你的情况下的UI线程,但至少有XamlReader.LoadAsync
你应该能够异步调用而不会阻塞。
这是一个完整的概念验证代码(为简洁而省略了取消逻辑):
private async Task<FixedDocument> GenerateDocumentAsync(double length)
{
System.IO.Stream streamIn;
using (var worker = new DispatcherThread())
{
streamIn = await worker.Run(() =>
{
var doc = new FixedDocument()
{
Pages = { new PageContent() { Child = new FixedPage() {
Width = length, Height = length,
Children = { new TextBlock() { Text = "dummy page" }}}}}
};
var streamOut = new System.IO.MemoryStream();
XamlWriter.Save(doc, streamOut);
return streamOut;
});
}
streamIn.Seek(0, System.IO.SeekOrigin.Begin);
var xamlReader = new XamlReader();
var tcs = new TaskCompletionSource<bool>();
AsyncCompletedEventHandler loadCompleted = (s, a) =>
{
if (a.Error != null)
tcs.TrySetException(a.Error);
else
tcs.TrySetResult(true);
};
xamlReader.LoadCompleted += loadCompleted;
try
{
var doc = xamlReader.LoadAsync(streamIn);
await tcs.Task;
return (FixedDocument)doc;
}
finally
{
xamlReader.LoadCompleted -= loadCompleted;
}
}
public class DispatcherThread: IDisposable
{
readonly Thread _dispatcherThread;
readonly TaskScheduler _taskScheduler;
public DispatcherThread()
{
var tcs = new TaskCompletionSource<TaskScheduler>();
_dispatcherThread = new Thread(() =>
{
var dispatcher = Dispatcher.CurrentDispatcher;
dispatcher.InvokeAsync(() =>
tcs.SetResult(TaskScheduler.FromCurrentSynchronizationContext()));
Dispatcher.Run();
});
_dispatcherThread.SetApartmentState(ApartmentState.STA);
_dispatcherThread.IsBackground = false;
_dispatcherThread.Start();
_taskScheduler = tcs.Task.Result;
}
public void Dispose()
{
if (_dispatcherThread.IsAlive)
{
Run(() =>
Dispatcher.CurrentDispatcher.BeginInvokeShutdown(DispatcherPriority.Send));
_dispatcherThread.Join();
}
}
public Task Run(Action action, CancellationToken token = default(CancellationToken))
{
return Task.Factory.StartNew(action, token, TaskCreationOptions.None, _taskScheduler);
}
public Task<T> Run<T>(Func<T> func, CancellationToken token = default(CancellationToken))
{
return Task.Factory.StartNew(func, token, TaskCreationOptions.None, _taskScheduler);
}
public Task Run(Func<Task> func, CancellationToken token = default(CancellationToken))
{
return Task.Factory.StartNew(func, token, TaskCreationOptions.None, _taskScheduler).Unwrap();
}
public Task<T> Run<T>(Func<Task<T>> func, CancellationToken token = default(CancellationToken))
{
return Task.Factory.StartNew(func, token, TaskCreationOptions.None, _taskScheduler).Unwrap();
}
}