Wpf 在不阻塞UI的情况下构建FixedDocument
我正在为生成一个文档。它很慢,所以我想释放UI线程。使用async/await,我得到一个异常,它说,“调用线程必须是STA”。我认为我需要整理UI线程传递/返回的值,但我似乎无法使其正常工作。我已经尝试了Dispatcher.Invoke的各种方法 有人知道如何使用async/await实现这一点吗 下面是一个可以粘贴到新的WPF项目(WpfApp1)中的瘦而有效的示例:Wpf 在不阻塞UI的情况下构建FixedDocument,wpf,async-await,Wpf,Async Await,我正在为生成一个文档。它很慢,所以我想释放UI线程。使用async/await,我得到一个异常,它说,“调用线程必须是STA”。我认为我需要整理UI线程传递/返回的值,但我似乎无法使其正常工作。我已经尝试了Dispatcher.Invoke的各种方法 有人知道如何使用async/await实现这一点吗 下面是一个可以粘贴到新的WPF项目(WpfApp1)中的瘦而有效的示例: public分部类主窗口:窗口,INotifyPropertyChanged { 公共主窗口() { 初始化组件();
public分部类主窗口:窗口,INotifyPropertyChanged
{
公共主窗口()
{
初始化组件();
RebuildDocument();//调用了多个位置
}
公共双倍长度{get;set;}=100;
固定文件;
公共固定文档
{
获取{返回文档;}
设置{if(document==value)返回;document=value;OnPropertyChanged();}
}
异步void重建文档()
{
文件=等待生成的文件(长度);
}
专用静态异步任务生成文档(双倍长度)
{
返回等待任务。运行(()=>
{
//假工作
返回新的FixedDocument(){
Pages={new PageContent(){Child=new FixedPage(){
宽度=长度,高度=长度,
Children={new TextBlock(){Text=“dummy page”};
});
}
公共事件属性更改事件处理程序属性更改;
void OnPropertyChanged([CallerMemberName]字符串propertyName=null)
{PropertyChanged?.Invoke(这个,新的PropertyChangedEventArgs(propertyName));}
}
我至少可以想到两种方法:
- 在UI线程的后台任务中构建您的
,同时在最内部的循环中大量生成(并观察取消):FixedDocument
公共类DispatcherThread:IDisposable { 只读线程\u dispatcherThread; 只读TaskScheduler\u TaskScheduler; 公共DispatcherThread() { var tcs=new TaskCompletionSource(); _dispatcherThread=新线程(()=> { var dispatcher=dispatcher.CurrentDispatcher; dispatcher.InvokeAsync(()=> SetResult(TaskScheduler.FromCurrentSynchronizationContext()); Dispatcher.Run(); }); _dispatcherThread.SetApartmentState(ApartmentState.STA); _dispatcherThread.IsBackground=false; _dispatcherThread.Start(); _taskScheduler=tcs.Task.Result; } 公共空间处置() { if(_dispatcherThread.IsAlive) { 运行(()=> Dispatcher.CurrentDispatcher.BeginInvokeShutdown(DispatcherPriority.Send)); _dispatcherThread.Join(); } } 公共任务运行(操作操作,CancellationToken=默认值(CancellationToken)) { 返回Task.Factory.StartNew(操作、令牌、TaskCreationOptions.None、\u taskScheduler); } 公共任务运行(Func Func,CancellationToken token=默认值(CancellationToken)) { 返回Task.Factory.StartNew(func、token、TaskCreationOptions.None、_taskScheduler); } 公共任务运行(Func Func,CancellationToken token=默认值(CancellationToken)) { 返回Task.Factory.StartNew(func,token,TaskCreationOptions.None,_taskScheduler.Unwrap(); } 公共任务运行(Func Func,CancellationToken token=默认值(CancellationToken)) { 返回Task.Factory.StartNew(func,token,TaskCreationOptions.None,_taskScheduler.Unwrap(); } }
ui元素
。因此不能在后台线程中实例化。例外情况来自任务。Run
@Aron高度参与文档处理和可视化,我怀疑您将MVVM模式应用于大型复杂的FixedDocument
。虽然document对象本身可以(也应该)是ViewModel的一个可绑定属性,但您不希望对其单个UIElement
子对象执行此操作,除非它是一个非常简单的文档。如果将MVVM应用于大型XAML文档的整个对象模型,UI体验将非常糟糕。FixedDocument
完全可以在后台线程上实例化(只要它是WPF Dispatcher线程,如回答中所述)。它不能跨不同的线程使用(就像@Vimes尝试的那样),但是可以通过序列化来克隆它。更多的是辅助UI线程。当然,这里有一些代码示例,其中一个应用程序运行多个UI线程,但当然,这里有龙。事实证明,我的问题不是页面计数,而是文档中的单个元素(Viewport3D)需要2秒才能构建。如果我在建造每一个之间进行,产量是可以接受的。我得到了与第二个解决方案类似的方法:将慢速元素渲染到RenderTargetBitmap,一旦冻结,这些位图就可以通过线程边界。如果有时间,我想切换到文档序列化/反序列化方法。只要LoadAsync做了我们期望的事情,就应该可以正常工作。问题与async
/wait
无关。问题是,GenerateDocument
的主体必须在STA线程中运行。因为这些是正在实例化的UI元素。尝试使用MVVM方法。
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);
}
}
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();
}
}