c# MVVM InvalidOperationException on BackgroundWorker when using Dispatcher
我目前在C#WPF中遇到问题。我编写了一个应用程序,该应用程序在后台任务中生成长时间运行的报告。我将棱镜与MVVM结合使用,并尝试通过Async ICommand实现和BackgroundWorker运行昂贵的后台任务。但是当我尝试检索生成的报告
1 | Report = asyncTask.Result; |
我收到一个InvalidOperationException声明,"调用线程无法访问此对象,因为另一个线程拥有它。"
是的,我已经尝试调用调度程序(当您搜索异常消息时,它会在google,stackoverflow等上找到的第一件事)。我已经尝试了多种变体,例如:
1 | Dispatcher.CurrentDispatcher.Invoke(() => Report = asyncTaks.Result); |
或
1 | Report.Dispatcher.Invoke(() => Report = asyncTask.Result); |
但是每次我收到此异常时。
我怀疑我调用报告UI的方式不充分。
结构简要如下:
1 2 3 4 5 6 7 | MainWindowViewModel -> SubWindowCommand SubWindowViewModel -> GenerateReportCommand ReportViewModel -> GenerateReportAsyncCommand <- Exception on callback |
我没主意了,有人知道我可能做错了什么吗?
下面是一些代码片段
报表生成器视图模型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | public class ReportFlowDocumentViewModel : BindableBase { private IUnityContainer _container; private bool _isReportGenerationInProgress; private FlowDocument _report; public FlowDocument Report { get { return _report; } set { if (object.Equals(_report, value) == false) { SetProperty(ref _report, value); } } } public bool IsReportGenerationInProgress { get { return _isReportGenerationInProgress; } set { if (_isReportGenerationInProgress != value) { SetProperty(ref _isReportGenerationInProgress, value); } } } public ReportFlowDocumentView View { get; set; } public DelegateCommand PrintCommand { get; set; } public AsyncCommand GenerateReportCommand { get; set; } public ReportFlowDocumentViewModel(ReportFlowDocumentView view, IUnityContainer c) { _container = c; view.DataContext = this; View = view; view.ViewModel = this; InitializeGenerateReportAsyncCommand(); IsReportGenerationInProgress = false; } private void InitializeGenerateReportAsyncCommand() { GenerateReportCommand = new CreateReportAsyncCommand(_container); GenerateReportCommand.RunWorkerStarting += (sender, args) => { IsReportGenerationInProgress = true; var reportGeneratorService = new ReportGeneratorService(); _container.RegisterInstance<ReportGeneratorService>(reportGeneratorService); }; GenerateReportCommand.RunWorkerCompleted += (sender, args) => { IsReportGenerationInProgress = false; var report = GenerateReportCommand.Result as FlowDocument; var dispatcher = Application.Current.MainWindow.Dispatcher; try { dispatcher.VerifyAccess(); if (Report == null) { Report = new FlowDocument(); } Dispatcher.CurrentDispatcher.Invoke(() => { Report = report; }); } catch (InvalidOperationException inex) { // here goes my exception } }; } public void TriggerReportGeneration() { GenerateReportCommand.Execute(null); } } |
这是我启动ReportView窗口的方式
1 2 3 4 5 6 7 8 9 10 11 | var reportViewModel = _container.Resolve<ReportFlowDocumentViewModel>(); View.ReportViewerWindowAction.WindowContent = reportViewModel.View; reportViewModel.TriggerReportGeneration(); var popupNotification = new Notification() { Title ="Report Viewer", }; ShowReportViewerRequest.Raise(popupNotification); |
与
1 |
AsyncCommand定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | public abstract class AsyncCommand : ICommand { public event EventHandler CanExecuteChanged; public event EventHandler RunWorkerStarting; public event RunWorkerCompletedEventHandler RunWorkerCompleted; public abstract object Result { get; protected set; } private bool _isExecuting; public bool IsExecuting { get { return _isExecuting; } private set { _isExecuting = value; if (CanExecuteChanged != null) CanExecuteChanged(this, EventArgs.Empty); } } protected abstract void OnExecute(object parameter); public void Execute(object parameter) { try { onRunWorkerStarting(); var worker = new BackgroundWorker(); worker.DoWork += ((sender, e) => OnExecute(e.Argument)); worker.RunWorkerCompleted += ((sender, e) => onRunWorkerCompleted(e)); worker.RunWorkerAsync(parameter); } catch (Exception ex) { onRunWorkerCompleted(new RunWorkerCompletedEventArgs(null, ex, true)); } } private void onRunWorkerStarting() { IsExecuting = true; if (RunWorkerStarting != null) RunWorkerStarting(this, EventArgs.Empty); } private void onRunWorkerCompleted(RunWorkerCompletedEventArgs e) { IsExecuting = false; if (RunWorkerCompleted != null) RunWorkerCompleted(this, e); } public virtual bool CanExecute(object parameter) { return !IsExecuting; } } |
CreateReportAsyncCommand:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public class CreateReportAsyncCommand : AsyncCommand { private IUnityContainer _container; public CreateReportAsyncCommand(IUnityContainer container) { _container = container; } public override object Result { get; protected set; } protected override void OnExecute(object parameter) { var reportGeneratorService = _container.Resolve<ReportGeneratorService>(); Result = reportGeneratorService?.GenerateReport(); } } |
我想我现在明白我的问题了。我不能在BackgroundThread中使用FlowDocument,然后再对其进行更新,对吗?
那么我如何在后台线程中创建FlowDocument,或者至少异步生成文档?
我正在创建的FlowDocument包含很多表,并且当我同步运行报告生成时,UI冻结大约30秒,这对于常规使用是不可接受的。
编辑:
在这里找到解决方案:
在BackgroundWorker线程上创建FlowDocument
简而言之:我在ReportGeneratorService中创建一个流文档,然后将FlowDocument序列化为字符串。在我的后台工作程序回调中,我接收到序列化的字符串并反序列化它-都使用XamlWriter和XmlReader进行显示,如下所示:
您的问题是,您在另一个线程中创建了FlowDocument。将数据放到非GUI容器中,并在bg返回UI线程后使用它们。