使用Dispatcher

时间:2017-11-24 09:20:45

标签: c# wpf multithreading mvvm

我目前在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();
    }

}

2 个答案:

答案 0 :(得分:1)

我想我现在理解我的问题了。我不能在BackgroundThread中使用FlowDocument并在之后更新它,对吗?

那么如何在后台线程中创建FlowDocument,或者至少异步生成文档呢?

我正在创建的FlowDocument包含很多表,当我同步运行报表生成时,UI会冻结大约30秒,这对于常规使用是不可接受的。

修改: 在这里找到解决方案: Creating FlowDocument on BackgroundWorker thread

简而言之:我在ReportGeneratorService中创建了一个流文档,然后将FlowDocument序列化为字符串。在我的后台工作程序回调中,我收到序列化字符串并反序列化它 - 使用XamlWriter和XmlReader,如图所示here

答案 1 :(得分:0)

您的问题是您在另一个线程中创建FlowDocument。将数据放入非GUI容器,并在bg返回UI线程后使用它们。