我目前在C#WPF中遇到问题。我写了一个应用程序,它在后台任务中生成长时间运行的报告。我正在使用MVVM和使用Async ICommand实现和BackgroundWorker运行昂贵的后台任务。但是当我尝试检索生成的报告时
Report = asyncTask.Result;
我得到一个InvalidOperationException,声明"调用线程无法访问此对象,因为另一个线程拥有它。"。
是的,我已经尝试调用调度程序(当您搜索异常消息时,它会在google,stackoverflow等上找到它的第一件事)。我尝试了几种变体,例如:
Dispatcher.CurrentDispatcher.Invoke(() => Report = asyncTaks.Result);
或
Report.Dispatcher.Invoke(() => Report = asyncTask.Result);
但每次我都得到这个例外。
我怀疑我调用报告界面的方式是不够的。
结构简述如下:
MainWindowViewModel
-> SubWindowCommand
SubWindowViewModel
-> GenerateReportCommand
ReportViewModel
-> GenerateReportAsyncCommand
<- Exception on callback
我没有想法,有没有人知道我可能做错了什么?
以下是一些代码片段
报告生成器视图模型:
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窗口的方法
var reportViewModel = _container.Resolve<ReportFlowDocumentViewModel>();
View.ReportViewerWindowAction.WindowContent = reportViewModel.View;
reportViewModel.TriggerReportGeneration();
var popupNotification = new Notification()
{
Title = "Report Viewer",
};
ShowReportViewerRequest.Raise(popupNotification);
带
ShowReportViewerRequest = new InteractionRequest<INotification>();
AsyncCommand定义
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:
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();
}
}
答案 0 :(得分:1)
我想我现在理解我的问题了。我不能在BackgroundThread中使用FlowDocument并在之后更新它,对吗?
那么如何在后台线程中创建FlowDocument,或者至少异步生成文档呢?
我正在创建的FlowDocument包含很多表,当我同步运行报表生成时,UI会冻结大约30秒,这对于常规使用是不可接受的。
修改强>: 在这里找到解决方案: Creating FlowDocument on BackgroundWorker thread
简而言之:我在ReportGeneratorService中创建了一个流文档,然后将FlowDocument序列化为字符串。在我的后台工作程序回调中,我收到序列化字符串并反序列化它 - 使用XamlWriter和XmlReader,如图所示here
答案 1 :(得分:0)
您的问题是您在另一个线程中创建FlowDocument。将数据放入非GUI容器,并在bg返回UI线程后使用它们。