Wpf 在不阻塞UI的情况下构建FixedDocument

Wpf 在不阻塞UI的情况下构建FixedDocument,wpf,async-await,Wpf,Async Await,我正在为生成一个文档。它很慢,所以我想释放UI线程。使用async/await,我得到一个异常,它说,“调用线程必须是STA”。我认为我需要整理UI线程传递/返回的值,但我似乎无法使其正常工作。我已经尝试了Dispatcher.Invoke的各种方法 有人知道如何使用async/await实现这一点吗 下面是一个可以粘贴到新的WPF项目(WpfApp1)中的瘦而有效的示例: public分部类主窗口:窗口,INotifyPropertyChanged { 公共主窗口() { 初始化组件();

我正在为生成一个文档。它很慢,所以我想释放UI线程。使用async/await,我得到一个异常,它说,“调用线程必须是STA”。我认为我需要整理UI线程传递/返回的值,但我似乎无法使其正常工作。我已经尝试了Dispatcher.Invoke的各种方法

有人知道如何使用async/await实现这一点吗

下面是一个可以粘贴到新的WPF项目(WpfApp1)中的瘦而有效的示例:


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();
    }
    }
    

问题不是异步/等待问题。问题是代码不是MVVM<代码>页面内容:
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();
    }
}