2017-11-24 7 views
0

현재 C# WPF에서 문제가 있습니다. 백그라운드 작업에서 장기 실행 보고서를 생성하는 응용 프로그램을 작성했습니다. 프리즘을 MVVM과 함께 사용하고 비동기 ICommand 구현과 BackgroundWorker로 비싼 백그라운드 작업을 실행하려고합니다. 난 결과 보고서Dispatcher를 사용할 때 BackgroundWorker에서 MVVM InvalidOperationException이 발생했습니다.

Report = asyncTask.Result; 

를 검색 할 때하지만 난라는 InvalidOperationException이 얻을 "다른 스레드가 그것을 소유하고 있기 때문에 호출 스레드가이 개체에 액세스 할 수 있습니다.".

예, 예외 메시지를 검색 할 때 Google에서 가장 먼저 발견 할 것으로 보이는 발송자 (예 : stackoverflow 등)를 이미 호출하려고했습니다. 나는 예를 들어 같은 몇 가지 변종을 시도 :

Dispatcher.CurrentDispatcher.Invoke(() => Report = asyncTaks.Result); 

또는

Report.Dispatcher.Invoke(() => Report = asyncTask.Result); 

하지만 때마다 내가이 예외를 얻을.

보고서 UI를 호출하는 방식이 적절하지 않다고 의심됩니다. 내가 생각에서입니다

MainWindowViewModel 
    -> SubWindowCommand 
     SubWindowViewModel 
     -> GenerateReportCommand 
      ReportViewModel 
       -> GenerateReportAsyncCommand 
       <- Exception on callback 

는 아무도 내가 잘못된 일을 할 수있는 어떤 단서가 않습니다 다음과 같이

구조는 간략하게 보인다? 내가

으로 ReportView 창

var reportViewModel = _container.Resolve<ReportFlowDocumentViewModel>(); 

View.ReportViewerWindowAction.WindowContent = reportViewModel.View; 

reportViewModel.TriggerReportGeneration(); 

var popupNotification = new Notification() 
{ 
    Title = "Report Viewer", 
}; 
ShowReportViewerRequest.Raise(popupNotification); 

을 시작하는 방법

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

} 

이것은 : 아래


보고서 생성기보기 모델 몇 가지 코드 조각입니다 10

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

'BackgroundWorker.DoWork' 이벤트 처리기를 사용하여 백그라운드 스레드에서 보고서를 생성하지만 액세스하려고합니다. 나중에 메인 스레드에서. 따라서 예외. – dymanoid

+0

'Dispatcher.CurrentDispatcher.Invoke'는 UI 스레드를 사용합니다. UI 요소를 업데이트하지만 데이터 바인딩에 INotifyPropertyChanged가 있기 때문에 MVAVM에서 유용하지 않습니다. –

+0

보고서는 FlowDocument UI 요소에 바인딩되어 있으므로 사용할 수 있다고 생각합니다. Dispatcher.CurrentDispatcher.Invoke를 사용할 수 없다면, 그 밖의 무엇입니까? – Michael

답변

1

나는 지금 내 문제를 이해한다고 생각한다. BackgroundThread에서 FlowDocument를 사용할 수없고 나중에 업데이트 할 수 있습니까?

그래서 백그라운드 스레드 내에서 FlowDocument를 만들거나 적어도 비동기 적으로 문서를 생성 할 수 있습니까?

내가 만드는 FlowDocument에는 많은 테이블이 포함되어 있으며 보고서 생성을 동 기적으로 실행하면 약 30 초 동안 UI가 정지되어 정상적인 사용에는 용납되지 않습니다.

편집 : 이 솔루션 여기 찾았 Creating FlowDocument on BackgroundWorker thread

간단히 : 내 ReportGeneratorService 내에서 유동 문서를 작성하고 난 문자열로 FlowDocument를 직렬화. 내 백그라운드 작업자 콜백에서 직렬화 문자열을 받고 deserialize - 그림과 같이 XamlWriter와 XmlReader로 모두 here

0

문제가 다른 스레드에서 FlowDocument를 만들 수 있다는 것입니다. bg가 UI 스레드로 돌아온 후에 비 GUI 컨테이너에 데이터를 저장하고 사용하십시오.

+0

이제 FlowDocument를 생성자 매개 변수로 사용하도록 ReportGeneratorService를 수정했습니다. 내 ViewModel 내에서 보고서의 새 인스턴스를 만들어 ReportGeneratorService에 전달합니다. 그런 다음 GenerateReportCommand 및 GenerateReportCommandAsync.Execute (..)를 통해 ReportGeneratorService 인스턴스를 전달합니다. 그러나 GenerateReportCommandAsync가 콜백으로 반환 할 때 여전히 예외가 발생합니다. – Michael

+0

더 명확하게하려면 - GUI (presentationframework, System.Windows 등)에 대한 모든 참조를 VM 프로젝트에서 삭제 한 다음 컴파일하려고 시도합니다. 오류를보고 수정하십시오. – Rekshino