diff --git a/Glyssen/Dialogs/GenerateGroupsProgressDialog.cs b/Glyssen/Dialogs/GenerateGroupsProgressDialog.cs index 9e75b7f7f..de30e6667 100644 --- a/Glyssen/Dialogs/GenerateGroupsProgressDialog.cs +++ b/Glyssen/Dialogs/GenerateGroupsProgressDialog.cs @@ -1,6 +1,8 @@ using System; using System.ComponentModel; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using System.Windows.Forms; using GlyssenEngine; using GlyssenEngine.Bundle; @@ -13,10 +15,12 @@ namespace Glyssen.Dialogs { class GenerateGroupsProgressDialog : ProgressDialogWithAcknowledgement { + private readonly CharacterGroupGenerator m_generator; private readonly string m_sizeInfo; - public GenerateGroupsProgressDialog(Project project, DoWorkEventHandler doWorkEventHandler, bool firstRun, bool replaceCancelButtonWithLink = false) + public GenerateGroupsProgressDialog(Project project, CharacterGroupGenerator generator, bool firstRun, bool replaceCancelButtonWithLink = false) { + m_generator = generator; InitializeComponent(); Text = LocalizationManager.GetString("DialogBoxes.GenerateGroupsProgressDialog.Title", "Generating Groups"); @@ -60,18 +64,18 @@ public GenerateGroupsProgressDialog(Project project, DoWorkEventHandler doWorkEv ProgressLabelTextWhenComplete = LocalizationManager.GetString("DialogBoxes.GenerateGroupsProgressDialog.Complete", "Group generation is complete."); BarStyle = ProgressBarStyle.Marquee; - BackgroundWorker worker = new BackgroundWorker(); - worker.WorkerSupportsCancellation = true; - worker.DoWork += doWorkEventHandler; - BackgroundWorker = worker; } - protected override void OnBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) + protected override void DoWork(CancellationToken cancellationToken) { - if (e.Error != null) + try + { + m_generator.GenerateCharacterGroups(cancellationToken); + } + catch { if (!CanCancel) - throw e.Error; + throw; var msg = LocalizationManager.GetString("DialogBoxes.GenerateGroupsProgressDialog.GenerationFailed", "New character groups could not be generated to satisfy the project settings for the current cast size. ({0})", "Parameter is a statement about the number of voice actors or planned cast size."); @@ -79,8 +83,6 @@ protected override void OnBackgroundWorker_RunWorkerCompleted(object sender, Run DialogResult = DialogResult.Cancel; Close(); } - else - base.OnBackgroundWorker_RunWorkerCompleted(sender, e); } private void InitializeComponent() @@ -100,11 +102,10 @@ public static void GenerateGroupsWithProgress(Project project, bool attemptToPre if (forceMatchToActors) project.CharacterGroupGenerationPreferences.CastSizeOption = CastSizeOption.MatchVoiceActorList; bool saveGroups = false; - using (var progressDialog = new GenerateGroupsProgressDialog(project, OnGenerateGroupsWorkerDoWork, firstGroupGenerationRun, cancelLink)) - { - var generator = new CharacterGroupGenerator(project, ghostCastSize, progressDialog.BackgroundWorker); - progressDialog.ProgressState.Arguments = generator; + var generator = new CharacterGroupGenerator(project, ghostCastSize); + using (var progressDialog = new GenerateGroupsProgressDialog(project, generator, firstGroupGenerationRun, cancelLink)) + { if (progressDialog.ShowDialog() == DialogResult.OK && generator.GeneratedGroups != null) { var assignedBefore = project.CharacterGroupList.CountVoiceActorsAssigned(); @@ -124,11 +125,5 @@ public static void GenerateGroupsWithProgress(Project project, bool attemptToPre } project.Save(saveGroups); } - - private static void OnGenerateGroupsWorkerDoWork(object s, DoWorkEventArgs e) - { - var generator = (CharacterGroupGenerator)((ProgressState)e.Argument).Arguments; - generator.GenerateCharacterGroups(); - } } } diff --git a/Glyssen/Dialogs/ProgressDialogWithAcknowledgement.cs b/Glyssen/Dialogs/ProgressDialogWithAcknowledgement.cs index 2b6f53bab..6aef5c4db 100644 --- a/Glyssen/Dialogs/ProgressDialogWithAcknowledgement.cs +++ b/Glyssen/Dialogs/ProgressDialogWithAcknowledgement.cs @@ -5,29 +5,30 @@ using System.Diagnostics; using System.Drawing; using System.Globalization; +using System.Threading; +using System.Threading.Tasks; using System.Windows.Forms; using L10NSharp; using SIL.Progress; using SIL.Reporting; +using Timer = System.Windows.Forms.Timer; namespace Glyssen.Dialogs { /// - /// Provides a progress dialog which forces the user to acknowledge is complete by clicking OK + /// Provides a progress dialog which forces the user to acknowledge completion by clicking OK /// - public class ProgressDialogWithAcknowledgement : Form + public class ProgressDialogWithAcknowledgement : Form, IProgress { public delegate void ProgressCallback(int progress); private Label m_statusLabel; private ProgressBar m_progressBar; private Label m_progressLabel; - private Timer m_showWindowIfTakingLongTimeTimer; private Timer m_progressTimer; private Label m_overviewLabel; private DateTime m_startTime; - private BackgroundWorker m_backgroundWorker; - private ProgressState m_progressState; + private CancellationTokenSource m_cancellationTokenSource; private TableLayoutPanel m_tableLayout; private bool m_workerStarted; private Button m_okButton; @@ -92,14 +93,8 @@ private void HandleTableLayoutSizeChanged(object sender, EventArgs e) /// public string StatusText { - get - { - return m_statusLabel.Text; - } - set - { - m_statusLabel.Text = value; - } + get => m_statusLabel.Text; + set => m_statusLabel.Text = value; } /// @@ -107,49 +102,27 @@ public string StatusText /// public string Overview { - get - { - return m_overviewLabel.Text; - } - set - { - m_overviewLabel.Text = value; - } + get => m_overviewLabel.Text; + set => m_overviewLabel.Text = value; } /// /// Get / set the minimum range of the progress bar /// - public int ProgressRangeMinimum + private int ProgressRangeMinimum { - get - { - return m_progressBar.Minimum; - } - set - { - if (m_backgroundWorker == null) - { - m_progressBar.Minimum = value; - } - } + get => m_progressBar.Minimum; + set => m_progressBar.Minimum = value; } /// /// Get / set the maximum range of the progress bar /// - public int ProgressRangeMaximum + private int ProgressRangeMaximum { - get - { - return m_progressBar.Maximum; - } + get => m_progressBar.Maximum; set { - if (m_backgroundWorker != null) - { - return; - } if (InvokeRequired) { Invoke(new ProgressCallback(SetMaximumCrossThread), value); @@ -171,10 +144,7 @@ private void SetMaximumCrossThread(int amount) /// public int Progress { - get - { - return m_progressBar.Value; - } + get => m_progressBar.Value; set { /* these were causing weird, hard to debug (because of threads) @@ -203,12 +173,10 @@ public int Progress /// public bool CanCancel { - get - { - return m_cancelButton.Enabled || m_cancelLink.Enabled; - } + get => m_cancellationTokenSource != null; set { + m_cancellationTokenSource = value ? new CancellationTokenSource() : null; if (ReplaceCancelButtonWithLink) { m_cancelLink.Enabled = value; @@ -222,70 +190,85 @@ public bool CanCancel } } + private CancellationToken CancellationToken => m_cancellationTokenSource?.Token ?? new CancellationToken(false); + /// - /// If this is set before showing, the dialog will run the worker and respond - /// to its events + /// This should be an abstract, but abstract dialogs are hard to work with in Designer. + /// Override this in subclass to have the dialog kick off the work when shown. /// - public BackgroundWorker BackgroundWorker + protected virtual void DoWork(CancellationToken cancellationToken) { - get - { - return m_backgroundWorker; - } - set - { - m_backgroundWorker = value; - m_progressBar.Minimum = 0; - m_progressBar.Maximum = 100; - } + } - public ProgressState ProgressStateResult - { - get - { - return m_progressState; - } - } + ///// + ///// If this is set before showing, the dialog will run the worker and respond + ///// to its events + ///// + //public BackgroundWorker BackgroundWorker + //{ + // get + // { + // return m_backgroundWorker; + // } + // set + // { + // m_backgroundWorker = value; + // m_progressBar.Minimum = 0; + // m_progressBar.Maximum = 100; + // } + //} + + //public ProgressState ProgressStateResult + //{ + // get + // { + // return m_progressState; + // } + //} /// /// Gets or sets the manner in which progress should be indicated on the progress bar. /// - public ProgressBarStyle BarStyle { get { return m_progressBar.Style; } set { m_progressBar.Style = value; } } - - /// - /// Optional; one will be created (of some class or subclass) if you don't set it. - /// E.g. dlg.ProgressState = new BackgroundWorkerState(dlg.BackgroundWorker); - /// Also, you can use the getter to gain access to the progressstate, in order to add arguments - /// which the worker method can get at. - /// - public ProgressState ProgressState + public ProgressBarStyle BarStyle { - get - { - if(m_progressState ==null) - { - if(m_backgroundWorker == null) - { - throw new ArgumentException("You must set BackgroundWorker before accessing this property."); - } - ProgressState = new BackgroundWorkerState(m_backgroundWorker); - } - return m_progressState; - } - - set - { - if (m_progressState!=null) - { - CancelRequested -= m_progressState.CancelRequested; - } - m_progressState = value; - CancelRequested += m_progressState.CancelRequested; - m_progressState.TotalNumberOfStepsChanged += OnTotalNumberOfStepsChanged; - } + get => m_progressBar.Style; + set => m_progressBar.Style = value; } + ///// + ///// Optional; one will be created (of some class or subclass) if you don't set it. + ///// E.g. dlg.ProgressState = new BackgroundWorkerState(dlg.BackgroundWorker); + ///// Also, you can use the getter to gain access to the ProgressState, in order to add arguments + ///// which the worker method can get at. + ///// + //public ProgressState ProgressState + //{ + // get + // { + // if (m_progressState ==null) + // { + // if(m_backgroundWorker == null) + // { + // throw new ArgumentException("You must set BackgroundWorker before accessing this property."); + // } + // ProgressState = new BackgroundWorkerState(m_backgroundWorker); + // } + // return m_progressState; + // } + + // set + // { + // if (m_progressState!=null) + // { + // CancelRequested -= m_progressState.CancelRequested; + // } + // m_progressState = value; + // CancelRequested += m_progressState.CancelRequested; + // m_progressState.TotalNumberOfStepsChanged += OnTotalNumberOfStepsChanged; + // } + //} + public string CancelLinkText { get => m_cancelLink.Text; @@ -310,39 +293,7 @@ public bool ReplaceCancelButtonWithLink } } - protected virtual void OnBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) - { - if(e.Cancelled || (ProgressStateResult != null && ProgressStateResult.Cancel)) - { - DialogResult = DialogResult.Cancel; - } - else if (ProgressStateResult != null && (ProgressStateResult.State == ProgressState.StateValue.StoppedWithError - || ProgressStateResult.ExceptionThatWasEncountered != null)) - { - //this dialog really can't know whether this was an unexpected exception or not - //so don't do this: Reporting.ErrorReporter.ReportException(ProgressStateResult.ExceptionThatWasEncountered, this, false); - DialogResult = DialogResult.Abort;//not really matching semantics - // _progressState.State = ProgressState.StateValue.StoppedWithError; - } - else - { - DialogResult = DialogResult.None; - m_progressBar.Maximum = 1; - m_progressBar.Value = 1; - m_progressBar.Style = ProgressBarStyle.Blocks; - - m_progressLabel.Text = ProgressLabelTextWhenComplete; - - AcceptButton = m_okButton; - - m_okButton.Text = OkButtonText ?? LocalizationManager.GetString("Common.OK", "OK"); - m_okButton.DialogResult = DialogResult.OK; - m_okButton.Enabled = true; - m_okButton.Visible = true; - } - } - - void OnBackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) + public void Report(ProgressChangedEventArgs e) { ProgressState state = e.UserState as ProgressState; if (state != null) @@ -350,8 +301,7 @@ void OnBackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs StatusText = state.StatusLabel; } - if (state == null - || state is BackgroundWorkerState) + if (state == null || state is BackgroundWorkerState) { Progress = e.ProgressPercentage; } @@ -371,13 +321,11 @@ void OnBackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs /// Raises the cancelled event /// /// Event data - protected virtual void OnCancelled( EventArgs e ) + private void OnCancelled( EventArgs e ) { - EventHandler cancelled = CancelRequested; - if( cancelled != null ) - { - cancelled( this, e ); - } + DialogResult = DialogResult.Cancel; + m_cancellationTokenSource.Cancel(); + CancelRequested?.Invoke(this, e); } /// @@ -385,14 +333,9 @@ protected virtual void OnCancelled( EventArgs e ) /// protected override void Dispose( bool disposing ) { - if( disposing ) - { - if (m_showWindowIfTakingLongTimeTimer != null) - { - m_showWindowIfTakingLongTimeTimer.Stop(); - } - } - base.Dispose( disposing ); + if (disposing) + components.Dispose(); + base.Dispose(disposing); } #region Windows Form Designer generated code @@ -406,7 +349,6 @@ private void InitializeComponent() this.m_statusLabel = new System.Windows.Forms.Label(); this.m_progressBar = new System.Windows.Forms.ProgressBar(); this.m_progressLabel = new System.Windows.Forms.Label(); - this.m_showWindowIfTakingLongTimeTimer = new System.Windows.Forms.Timer(this.components); this.m_progressTimer = new System.Windows.Forms.Timer(this.components); this.m_overviewLabel = new System.Windows.Forms.Label(); this.m_tableLayout = new System.Windows.Forms.TableLayoutPanel(); @@ -485,14 +427,8 @@ private void InitializeComponent() this.m_progressLabel.UseMnemonic = false; this.m_glyssenColorPalette.SetUsePaletteColors(this.m_progressLabel, true); // - // m_showWindowIfTakingLongTimeTimer - // - this.m_showWindowIfTakingLongTimeTimer.Interval = 2000; - this.m_showWindowIfTakingLongTimeTimer.Tick += new System.EventHandler(this.OnTakingLongTimeTimerClick); - // // m_progressTimer // - this.m_progressTimer.Enabled = true; this.m_progressTimer.Interval = 1000; this.m_progressTimer.Tick += new System.EventHandler(this.progressTimer_Tick); // @@ -666,8 +602,6 @@ private void InitializeComponent() this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.Text = "Palaso"; this.m_glyssenColorPalette.SetUsePaletteColors(this, true); - this.Load += new System.EventHandler(this.ProgressDialog_Load); - this.Shown += new System.EventHandler(this.ProgressDialog_Shown); this.m_tableLayout.ResumeLayout(false); this.m_tableLayout.PerformLayout(); this.m_buttonPanel.ResumeLayout(false); @@ -680,33 +614,14 @@ private void InitializeComponent() } #endregion - - private void OnTakingLongTimeTimerClick(object sender, EventArgs e) - { - // Show the window now the timer has elapsed, and stop the timer - m_showWindowIfTakingLongTimeTimer.Stop(); - if (!Visible) - { - Show(); - } - } - private void OnCancelButton_Click(object sender, EventArgs e) { - m_showWindowIfTakingLongTimeTimer.Stop(); - - //Debug.WriteLine("Dialog:OnCancelButton_Click"); - // Prevent further cancellation m_cancelButton.Enabled = false; m_progressTimer.Stop(); m_progressLabel.Text = LocalizationManager.GetString("DialogBoxes.ProgressDialogWithAcknowledgement.Canceling", "Canceling..."); // Tell people we're canceling - OnCancelled( e ); - if (m_backgroundWorker != null && m_backgroundWorker.WorkerSupportsCancellation) - { - m_backgroundWorker.CancelAsync(); - } + OnCancelled(e); } private void progressTimer_Tick(object sender, EventArgs e) @@ -744,90 +659,61 @@ private static string GetStringFor( TimeSpan span ) { return string.Format(CultureInfo.CurrentUICulture, "{0} day {1} hour", span.Days, span.Hours); } - else if( span.TotalHours > 1 ) + if( span.TotalHours > 1 ) { return string.Format(CultureInfo.CurrentUICulture, "{0} hour {1} minutes", span.Hours, span.Minutes); } - else if( span.TotalMinutes > 1 ) + if( span.TotalMinutes > 1 ) { return string.Format(CultureInfo.CurrentUICulture, "{0} minutes {1} seconds", span.Minutes, span.Seconds); } return string.Format( CultureInfo.CurrentUICulture, "{0} seconds", span.Seconds ); } - public void OnNumberOfStepsCompletedChanged(object sender, EventArgs e) - { - Progress = ((ProgressState) sender).NumberOfStepsCompleted; - //in case there is no event pump showing us (mono-threaded) - progressTimer_Tick(this, null); - Refresh(); - } - - public void OnTotalNumberOfStepsChanged(object sender, EventArgs e) - { - if (InvokeRequired) - { - Invoke(new ProgressCallback(UpdateTotal), ((ProgressState)sender).TotalNumberOfSteps); - } - else - { - UpdateTotal(((ProgressState) sender).TotalNumberOfSteps); - } - } - - private void UpdateTotal(int steps) - { - m_startTime = DateTime.Now; - ProgressRangeMaximum = steps; - Refresh(); - } - - public void OnStatusLabelChanged(object sender, EventArgs e) - { - StatusText = ((ProgressState)sender).StatusLabel; - Refresh(); - } - - private void OnStartWorker(object sender, EventArgs e) + private async Task OnStartWorker() { + if (m_workerStarted) + return; m_workerStarted = true; - //Debug.WriteLine("Dialog:StartWorker"); + m_progressTimer.Enabled = true; - if (m_backgroundWorker != null) + try { - //BW uses percentages (unless it's using our custom ProgressState in the UserState member) + // Progress is reported as percentage ProgressRangeMinimum = 0; ProgressRangeMaximum = 100; + await Task.Run(() => { DoWork(CancellationToken); }, CancellationToken); - //if the actual task can't take cancelling, the caller of this should set CanCancel to false; - m_backgroundWorker.WorkerSupportsCancellation = CanCancel; + DialogResult = DialogResult.None; + m_progressBar.Maximum = 1; + m_progressBar.Value = 1; + m_progressBar.Style = ProgressBarStyle.Blocks; - m_backgroundWorker.ProgressChanged += OnBackgroundWorker_ProgressChanged; - m_backgroundWorker.RunWorkerCompleted += OnBackgroundWorker_RunWorkerCompleted; - m_backgroundWorker.RunWorkerAsync(ProgressState); - } - } + m_progressLabel.Text = ProgressLabelTextWhenComplete; - //This is here, in addition to the OnShown handler, because of a weird bug where a certain, - //completely unrelated test (which doesn't use this class at all) can cause tests using this to - //fail because the OnShown event is never fired. - //I don't know why the orginal code we copied this from was using onshown instead of onload, - //but it may have something to do with its "delay show" feature (which I couldn't get to work, - //but which would be a terrific thing to have) - private void ProgressDialog_Load(object sender, EventArgs e) - { - if(!m_workerStarted) + AcceptButton = m_okButton; + + m_okButton.Text = OkButtonText ?? LocalizationManager.GetString("Common.OK", "OK"); + m_okButton.DialogResult = DialogResult.OK; + m_okButton.Enabled = true; + m_okButton.Visible = true; + m_okButton.Visible = true; + } + catch (Exception exception) { - OnStartWorker(this, null); + Console.WriteLine(exception); + // This dialog really can't know whether this was an unexpected exception or not, + // so don't do this: + // Reporting.ErrorReporter.ReportException(ProgressStateResult.ExceptionThatWasEncountered, this, false); + DialogResult = DialogResult.Abort; //not really matching semantics + Close(); } } - private void ProgressDialog_Shown(object sender, EventArgs e) + protected override async void OnActivated(EventArgs e) { - if(!m_workerStarted) - { - OnStartWorker(this, null); - } + base.OnActivated(e); + await OnStartWorker(); } private void OnCancelLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) diff --git a/Glyssen/MainForm.cs b/Glyssen/MainForm.cs index 32d3b9cbf..12cae261d 100644 --- a/Glyssen/MainForm.cs +++ b/Glyssen/MainForm.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics; using System.Drawing; using System.Globalization; @@ -26,7 +25,6 @@ using L10NSharp.UI; using SIL.DblBundle; using SIL.IO; -using SIL.Progress; using SIL.Reporting; using SIL.Windows.Forms; using SIL.Windows.Forms.Miscellaneous; @@ -185,6 +183,14 @@ private void SetProject(Project project) } private void FinishSetProjectIfReady(object sender, EventArgs e) + { + if (InvokeRequired) + Invoke(new Action(FinishSetProjectIfReady)); + else + FinishSetProjectIfReady(); + } + + private void FinishSetProjectIfReady() { if (m_project != null && (m_project.ProjectState & ProjectState.ReadyForUserInteraction) > 0) FinishSetProject(); @@ -890,11 +896,9 @@ private void EnsureGroupsAreInSynchWithCharactersInUse() var adjuster = new CharacterGroupsAdjuster(m_project); if (adjuster.GroupsAreNotInSynchWithData) { - using (var progressDialog = new GenerateGroupsProgressDialog(m_project, OnGenerateGroupsWorkerDoWork, false, true)) + var generator = new CharacterGroupGenerator(m_project, ProjectCastSizePlanningViewModel.SelectedCastSize); + using (var progressDialog = new GenerateGroupsProgressDialog(m_project, generator, false, true)) { - var generator = new CharacterGroupGenerator(m_project, ProjectCastSizePlanningViewModel.SelectedCastSize, progressDialog.BackgroundWorker); - progressDialog.ProgressState.Arguments = generator; - if (progressDialog.ShowDialog() == DialogResult.OK && generator.GeneratedGroups != null) { var assignedBefore = m_project.CharacterGroupList.CountVoiceActorsAssigned(); @@ -924,12 +928,6 @@ private void EnsureGroupsAreInSynchWithCharactersInUse() CharacterGroupList.AssignGroupIds(m_project.CharacterGroupList.CharacterGroups); } - private void OnGenerateGroupsWorkerDoWork(object s, DoWorkEventArgs e) - { - var generator = (CharacterGroupGenerator)((ProgressState)e.Argument).Arguments; - generator.GenerateCharacterGroups(); - } - private bool IsOkToExport(ProjectExporter exporter) { var export = true; diff --git a/GlyssenEngine/Project.cs b/GlyssenEngine/Project.cs index 942e56af2..e43d26045 100644 --- a/GlyssenEngine/Project.cs +++ b/GlyssenEngine/Project.cs @@ -193,7 +193,7 @@ public Project(GlyssenBundle bundle, string recordingProjectName = null, Project PopulateAndParseBooks(bundle); } - public Project(ParatextScrTextWrapper paratextProject) : + public Project(ParatextScrTextWrapper paratextProject, Action exceptionHandler = null) : this(paratextProject.GlyssenDblTextMetadata, null, false, paratextProject.WritingSystem) { Writer.SetUpProjectPersistence(this); @@ -203,7 +203,7 @@ public Project(ParatextScrTextWrapper paratextProject) : SetWsQuotationMarksUsingFullySpecifiedContinuers(paratextProject.QuotationMarks); } - ParseAndSetBooks(paratextProject.UsxDocumentsForIncludedBooks, paratextProject.Stylesheet); + ParseAndSetBooks(paratextProject.UsxDocumentsForIncludedBooks, paratextProject.Stylesheet, exceptionHandler); } /// @@ -337,7 +337,7 @@ public void SetQuoteSystem(QuoteSystemStatus status, QuoteSystem system) if (IsQuoteSystemReadyForParse && ProjectState == ProjectState.NeedsQuoteSystemConfirmation) { m_quoteSystem = system; - DoQuoteParse(); + DoQuoteParseAsync(); } else if ((quoteSystemChanged && !quoteSystemBeingSetForFirstTime) || (QuoteSystemStatus == QuoteSystemStatus.Reviewed && @@ -1379,7 +1379,7 @@ private void InitializeLoadedProject() m_usxPercentComplete = 100; if (QuoteSystem == null) { - GuessAtQuoteSystem(); + GuessAtQuoteSystemAsync(); UpdateControlFileVersion(); return; } @@ -1478,7 +1478,7 @@ public void IncludeExistingBook(BookScript book) m_books.Insert(i, book); } - public void IncludeBooksFromParatext(ParatextScrTextWrapper wrapper, ISet bookNumbers, Action postParseAction) + public async Task IncludeBooksFromParatext(ParatextScrTextWrapper wrapper, ISet bookNumbers, Action postParseAction) { wrapper.IncludeBooks(bookNumbers.Select(BCVRef.NumberToBookCode)); var usxBookInfoList = wrapper.GetUsxDocumentsForIncludedParatextBooks(bookNumbers); @@ -1491,65 +1491,72 @@ void EnhancedPostParseAction(BookScript book) postParseAction?.Invoke(book); } - ParseAndIncludeBooks(usxBookInfoList, wrapper.Stylesheet, EnhancedPostParseAction); + await ParseAndIncludeBooks(usxBookInfoList, wrapper.Stylesheet, null, EnhancedPostParseAction); } - private void ParseAndSetBooks(IEnumerable books, IStylesheet stylesheet) + private async Task ParseAndSetBooks(IEnumerable books, IStylesheet stylesheet, Action exceptionHandler = null) { if (m_books.Any()) throw new InvalidOperationException("Project already contains books. If the intention is to replace the existing ones, let's clear the list first. Otherwise, call ParseAndIncludeBooks."); - ParseAndIncludeBooks(books, stylesheet); + await ParseAndIncludeBooks(books, stylesheet, exceptionHandler); } - private void ParseAndIncludeBooks(IEnumerable books, IStylesheet stylesheet, Action postParseAction = null) + private async Task ParseAndIncludeBooks(IEnumerable books, IStylesheet stylesheet, + Action exceptionHandler/* = null*/, Action postParseAction = null) { if (Versification == null) throw new NullReferenceException("What!!!"); ProjectState = ProjectState.Initial | (ProjectState & ProjectState.WritingSystemRecoveryInProcess); - var usxWorker = new BackgroundWorker {WorkerReportsProgress = true}; - usxWorker.DoWork += UsxWorker_DoWork; - usxWorker.RunWorkerCompleted += UsxWorker_RunWorkerCompleted; - usxWorker.ProgressChanged += UsxWorker_ProgressChanged; + List parsedBooks = null; + try + { + await Task.Run(() => + { + parsedBooks = UsxParse(books, stylesheet, postParseAction); + }); - object[] parameters = {books, stylesheet, postParseAction}; - usxWorker.RunWorkerAsync(parameters); + await Task.Run(() => + { + if (QuoteSystem == null) + GuessAtQuoteSystem(); + else if (IsQuoteSystemReadyForParse) + DoQuoteParse(parsedBooks.Select(b => b.BookId)); + }); + } + catch (Exception e) + { + Console.WriteLine(e); + if (exceptionHandler == null) + ErrorReport.ReportFatalException(e); + else + exceptionHandler(e); + } } - private void UsxWorker_DoWork(object sender, DoWorkEventArgs e) + private List UsxParse(IEnumerable books, IStylesheet stylesheet, Action postParseAction = null) { - var parameters = (object[])e.Argument; - var books = (IEnumerable)parameters[0]; - var stylesheet = (IStylesheet)parameters[1]; - var postParseAction = parameters.Length > 2 ? (Action)parameters[2] : null; - - var backgroundWorker = (BackgroundWorker)sender; - - var parsedBooks = UsxParser.ParseBooks(books, stylesheet, i => backgroundWorker.ReportProgress(i)); + var parsedBooks = UsxParser.ParseBooks(books, stylesheet, i => + { + m_usxPercentComplete = i; + var pe = new ProgressChangedEventArgs(PercentInitialized, null); + OnReport(pe); + }); if (postParseAction != null) { foreach (var book in parsedBooks) postParseAction(book); } - e.Result = parsedBooks; - } - - private void UsxWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) - { - if (e.Error != null) - throw e.Error; - var bookScripts = (List)e.Result; - - foreach (var bookScript in bookScripts) + foreach (var bookScript in parsedBooks) { // This code is an attempt to figure out how we are getting null reference exceptions when using the objects in the list (See PG-275 & PG-287) if (bookScript?.BookId == null) { - var nonNullBookScripts = bookScripts.Where(b => b != null).Select(b => b.BookId); + var nonNullBookScripts = parsedBooks.Where(b => b != null).Select(b => b.BookId); var nonNullBookScriptsStr = Join(";", nonNullBookScripts); var initialMessage = bookScript == null ? "BookScript is null." : "BookScript has null BookId."; - throw new ApplicationException($"{initialMessage} Number of BookScripts: {bookScripts.Count}. " + + throw new ApplicationException($"{initialMessage} Number of BookScripts: {parsedBooks.Count}. " + $"BookScripts which are NOT null: {nonNullBookScriptsStr}"); } @@ -1560,12 +1567,12 @@ private void UsxWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEvent if (m_books.Any()) { - foreach (var book in bookScripts) + foreach (var book in parsedBooks) IncludeExistingBook(book); } else { - m_books.AddRange(bookScripts); + m_books.AddRange(parsedBooks); m_projectMetadata.ParserVersion = kParserVersion; if (m_books.All(b => IsNullOrEmpty(b.PageHeader))) ChapterAnnouncementStyle = ChapterAnnouncement.ChapterLabel; @@ -1574,80 +1581,55 @@ private void UsxWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEvent AddMissingAvailableBooks(); } - if (QuoteSystem == null) - GuessAtQuoteSystem(); - else if (IsQuoteSystemReadyForParse) - DoQuoteParse(bookScripts.Select(b => b.BookId)); + return parsedBooks; } - private void UsxWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) + private async Task GuessAtQuoteSystemAsync() { - m_usxPercentComplete = e.ProgressPercentage; - var pe = new ProgressChangedEventArgs(PercentInitialized, null); - OnReport(pe); + await Task.Run(GuessAtQuoteSystem); } private void GuessAtQuoteSystem() { ProjectState = ProjectState.UsxComplete | (ProjectState & ProjectState.WritingSystemRecoveryInProcess); - var guessWorker = new BackgroundWorker {WorkerReportsProgress = true}; - guessWorker.DoWork += GuessWorker_DoWork; - guessWorker.RunWorkerCompleted += GuessWorker_RunWorkerCompleted; - guessWorker.ProgressChanged += GuessWorker_ProgressChanged; - guessWorker.RunWorkerAsync(); - } - - private void GuessWorker_DoWork(object sender, DoWorkEventArgs e) - { - e.Result = QuoteSystemGuesser.Guess(ControlCharacterVerseData.Singleton, m_books, Versification, out _, - sender as BackgroundWorker); - } - - private void GuessWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) - { - if (e.Error != null) - throw e.Error; - - SetQuoteSystem(QuoteSystemStatus.Guessed, (QuoteSystem)e.Result); + var quoteSystem = QuoteSystemGuesser.Guess(ControlCharacterVerseData.Singleton, m_books, Versification, out _, + i => + { + m_guessPercentComplete = i; + var pe = new ProgressChangedEventArgs(PercentInitialized, null); + OnReport(pe); + }); + SetQuoteSystem(QuoteSystemStatus.Guessed, quoteSystem); Save(); } - private void GuessWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) + private async Task DoQuoteParseAsync(IEnumerable booksToParse = null) { - m_guessPercentComplete = e.ProgressPercentage; - var pe = new ProgressChangedEventArgs(PercentInitialized, null); - OnReport(pe); + await Task.Run(() => { DoQuoteParse(booksToParse);}); } - private void DoQuoteParse(IEnumerable booksToParse = null) + private void DoQuoteParse(IEnumerable bookIds) { m_projectMetadata.ParserVersion = kParserVersion; ProjectState = ProjectState.Parsing; - var quoteWorker = new BackgroundWorker {WorkerReportsProgress = true}; - quoteWorker.DoWork += QuoteWorker_DoWork; - quoteWorker.RunWorkerCompleted += QuoteWorker_RunWorkerCompleted; - quoteWorker.ProgressChanged += QuoteWorker_ProgressChanged; - object[] parameters = {booksToParse}; - quoteWorker.RunWorkerAsync(parameters); - } - - private void QuoteWorker_DoWork(object sender, DoWorkEventArgs e) - { - var bookIds = (IEnumerable)((object[])e.Argument)[0]; - QuoteParser.ParseProject(this, sender as BackgroundWorker, bookIds); - } - - private void QuoteWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) - { - if (e.Error != null) + try + { + QuoteParser.ParseProject(this, i => + { + m_quotePercentComplete = i; + var pe = new ProgressChangedEventArgs(PercentInitialized, null); + OnReport(pe); + }, bookIds); + } + catch (Exception e) { #if DEBUG Exception innerException; - if ((innerException = e.Error?.InnerException) != null) + if ((innerException = e.InnerException) != null) Debug.WriteLine(innerException.Message + innerException.StackTrace); #endif - throw e.Error; + throw; } ProjectState = ProjectState.QuoteParseComplete; @@ -1668,13 +1650,6 @@ private void QuoteWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEve QuoteParseCompleted?.Invoke(this, new EventArgs()); } - private void QuoteWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) - { - m_quotePercentComplete = e.ProgressPercentage; - var pe = new ProgressChangedEventArgs(PercentInitialized, null); - OnReport(pe); - } - public int MaxProjectNameLength => Writer.GetMaxProjectNameLength(this); public BookScript LoadExistingBookIfPossible(string bookId) diff --git a/GlyssenEngine/Quote/QuoteParser.cs b/GlyssenEngine/Quote/QuoteParser.cs index 665b40686..67605cf08 100644 --- a/GlyssenEngine/Quote/QuoteParser.cs +++ b/GlyssenEngine/Quote/QuoteParser.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.Linq; @@ -21,7 +20,7 @@ namespace GlyssenEngine.Quote { public class QuoteParser { - public static void ParseProject(Project project, BackgroundWorker projectWorker, IEnumerable bookIdsToParse) + public static void ParseProject(Project project, Action reportProgress, IEnumerable bookIdsToParse) { var cvInfo = new ParserCharacterRepository(new CombinedCharacterVerseData(project), project.ReferenceText); @@ -43,10 +42,10 @@ public static void ParseProject(Project project, BackgroundWorker projectWorker, { book.Blocks = new QuoteParser(cvInfo, book.BookId, blocksInBook[book], project.Versification).Parse().ToList(); completedProjectBlocks += numBlocksPerBook[book.BookId]; - projectWorker.ReportProgress(MathUtilities.Percent(completedProjectBlocks, allProjectBlocks, 99)); + reportProgress(MathUtilities.Percent(completedProjectBlocks, allProjectBlocks, 99)); }); - projectWorker.ReportProgress(100); + reportProgress(100); } public static void SetQuoteSystem(QuoteSystem quoteSystem) diff --git a/GlyssenEngine/Quote/QuoteSystemGuesser.cs b/GlyssenEngine/Quote/QuoteSystemGuesser.cs index e70e7e9d3..f5576260e 100644 --- a/GlyssenEngine/Quote/QuoteSystemGuesser.cs +++ b/GlyssenEngine/Quote/QuoteSystemGuesser.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics; using System.Linq; using Glyssen.Shared; @@ -37,13 +36,13 @@ private static void IncrementScore(Dictionary scores, QuoteSys bestScore = scores[quoteSystem]; } - public static QuoteSystem Guess(ICharacterVerseInfo cvInfo, List bookList, ScrVers versification, out bool certain, BackgroundWorker worker = null) where T : IScrBook + public static QuoteSystem Guess(ICharacterVerseInfo cvInfo, List bookList, ScrVers versification, out bool certain, Action reportProgress = null) where T : IScrBook { certain = false; var bookCount = bookList.Count; if (bookCount == 0) { - ReportProgressComplete(worker); + reportProgress?.Invoke(100); return QuoteSystem.Default; } var scores = QuoteSystem.UniquelyGuessableSystems.ToDictionary(s => s, s => 0); @@ -73,8 +72,7 @@ public static QuoteSystem Guess(ICharacterVerseInfo cvInfo, List bookList, var bookNum = bookTuple.Item1; var book = bookTuple.Item2; - if (worker != null) - worker.ReportProgress(MathUtilities.Percent(++booksProcessed, bookCount)); + reportProgress?.Invoke(MathUtilities.Percent(++booksProcessed, bookCount)); int versesAnalyzedForCurrentBook = 0; int prevQuoteChapter = -1; @@ -209,7 +207,7 @@ public static QuoteSystem Guess(ICharacterVerseInfo cvInfo, List bookList, if (competitors.Count == 1) { certain = true; - ReportProgressComplete(worker); + reportProgress?.Invoke(100); return competitors[0]; } @@ -297,7 +295,7 @@ public static QuoteSystem Guess(ICharacterVerseInfo cvInfo, List bookList, if (competitors.Any()) { - ReportProgressComplete(worker); + reportProgress?.Invoke(100); if (competitors.Count == 1) return competitors[0]; @@ -327,7 +325,7 @@ public static QuoteSystem Guess(ICharacterVerseInfo cvInfo, List bookList, #if SHOWTESTINFO Debug.WriteLine("Time-out guessing quote system."); #endif - ReportProgressComplete(worker); + reportProgress?.Invoke(100); return BestGuess(viableSystems, scores, bestScore, foundEndQuote); } @@ -335,7 +333,7 @@ public static QuoteSystem Guess(ICharacterVerseInfo cvInfo, List bookList, prevQuoteVerse = quote.Verse; } } - ReportProgressComplete(worker); + reportProgress?.Invoke(100); return BestGuess(viableSystems, scores, bestScore, foundEndQuote); } @@ -427,11 +425,5 @@ private static QuoteSystem BestGuess(IEnumerable viableSystems, Dic // newSystem.AllLevels.Add(QuoteUtils.GenerateLevel3(newSystem, true)); // return newSystem; //} - - private static void ReportProgressComplete(BackgroundWorker worker) - { - if (worker != null) - worker.ReportProgress(100); - } } } diff --git a/GlyssenEngine/Rules/CharacterGroupGenerator.cs b/GlyssenEngine/Rules/CharacterGroupGenerator.cs index 17073d561..eff7b5b42 100644 --- a/GlyssenEngine/Rules/CharacterGroupGenerator.cs +++ b/GlyssenEngine/Rules/CharacterGroupGenerator.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics; using System.Linq; +using System.Threading; using GlyssenEngine.Bundle; using GlyssenEngine.Casting; using GlyssenEngine.Character; @@ -18,7 +18,7 @@ public class CharacterGroupGenerator private readonly Project m_project; private readonly CastSizeRowValues m_ghostCastSize; private readonly Proximity m_proximity; - private readonly BackgroundWorker m_worker; + private CancellationToken m_cancellationToken; private static readonly SortedDictionary>> s_deityCharacters; @@ -45,12 +45,11 @@ static CharacterGroupGenerator() s_deityCharacters.Add(17, new List> { jesusSet, new HashSet { "God" }, holySpiritSet, new HashSet { CharacterVerse.kScriptureCharacter } }); } - public CharacterGroupGenerator(Project project, CastSizeRowValues ghostCastSize = null, BackgroundWorker worker = null) + public CharacterGroupGenerator(Project project, CastSizeRowValues ghostCastSize = null) { m_project = project; m_ghostCastSize = ghostCastSize; m_proximity = new Proximity(project); - m_worker = worker ?? new BackgroundWorker(); } public List GeneratedGroups { get; private set; } @@ -105,6 +104,13 @@ private static void LogAndOutputToDebugConsole(string message) public List GenerateCharacterGroups(bool enforceProximityAndGenderConstraints = false) { + return GenerateCharacterGroups(new CancellationToken(false), enforceProximityAndGenderConstraints); + } + + public List GenerateCharacterGroups(CancellationToken cancellationToken, bool enforceProximityAndGenderConstraints = false) + { + m_cancellationToken = cancellationToken; + m_project.SetDefaultCharacterGroupGenerationPreferences(); List actorsForGeneration; @@ -119,7 +125,7 @@ public List GenerateCharacterGroups(bool enforceProximityAndGend } List characterGroups = CreateGroupsForActors(actorsForGeneration); - if (m_worker.CancellationPending) + if (m_cancellationToken.IsCancellationRequested) { EnsureActorListIsSetToRealActors(realActorsToReset); return GeneratedGroups = null; @@ -133,7 +139,7 @@ public List GenerateCharacterGroups(bool enforceProximityAndGend List nonCameoActors = actorsForGeneration.Where(a => !a.IsCameo).ToList(); - if (m_worker.CancellationPending) + if (m_cancellationToken.IsCancellationRequested) { EnsureActorListIsSetToRealActors(realActorsToReset); return GeneratedGroups = null; @@ -151,7 +157,7 @@ public List GenerateCharacterGroups(bool enforceProximityAndGend IReadOnlyDictionary characterDetails = m_project.AllCharacterDetailDictionary; var includedCharacterDetails = characterDetails.Values.Where(c => characterIdsOrderedToMinimizeProximityConflicts.Select(e => e.Key).Contains(c.CharacterId)).ToList(); - if (m_worker.CancellationPending) + if (m_cancellationToken.IsCancellationRequested) { EnsureActorListIsSetToRealActors(realActorsToReset); return GeneratedGroups = null; @@ -194,7 +200,7 @@ public List GenerateCharacterGroups(bool enforceProximityAndGend } } - if (m_worker.CancellationPending) + if (m_cancellationToken.IsCancellationRequested) { EnsureActorListIsSetToRealActors(realActorsToReset); return GeneratedGroups = null; @@ -227,7 +233,7 @@ public List GenerateCharacterGroups(bool enforceProximityAndGend Debug.WriteLine("==========================================================="); } - if (m_worker.CancellationPending) + if (m_cancellationToken.IsCancellationRequested) { EnsureActorListIsSetToRealActors(realActorsToReset); return GeneratedGroups = null; @@ -250,7 +256,7 @@ public List GenerateCharacterGroups(bool enforceProximityAndGend { foreach (var configuration in trialConfigurationsForNarratorsAndExtras) { - if (m_worker.CancellationPending) + if (m_cancellationToken.IsCancellationRequested) { EnsureActorListIsSetToRealActors(realActorsToReset); return GeneratedGroups = null; @@ -360,7 +366,7 @@ private List GetFinalizedGroups(TrialGroupConfiguration configur { List groups = configuration.Groups; - if (m_worker.CancellationPending) + if (m_cancellationToken.IsCancellationRequested) { EnsureActorListIsSetToRealActors(realActorsToReset); return GeneratedGroups = null; diff --git a/GlyssenEngineTests/Rules/CharacterGroupGeneratorTests.cs b/GlyssenEngineTests/Rules/CharacterGroupGeneratorTests.cs index 5aea1d304..b4b849092 100644 --- a/GlyssenEngineTests/Rules/CharacterGroupGeneratorTests.cs +++ b/GlyssenEngineTests/Rules/CharacterGroupGeneratorTests.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Linq; using System.Threading; +using System.Threading.Tasks; using GlyssenEngine; using GlyssenEngine.Bundle; using GlyssenEngine.Casting; @@ -1477,7 +1478,7 @@ public void SetUp() } [Test] - public void GenerateCharacterGroups_IsCancelable() + public async Task GenerateCharacterGroups_Cancel_GenerationCancelsLeavingOriginalGroups() { SetVoiceActors(10); m_testProject.CharacterGroupList.CharacterGroups.Clear(); @@ -1485,22 +1486,10 @@ public void GenerateCharacterGroups_IsCancelable() group.AssignVoiceActor(m_testProject.VoiceActorList.AllActors[0].Id); m_testProject.CharacterGroupList.CharacterGroups.Add(group); - BackgroundWorker worker = new BackgroundWorker {WorkerSupportsCancellation = true}; - CharacterGroupGenerator generator = new CharacterGroupGenerator(m_testProject, null, worker); - worker.DoWork += (sender, args) => - { - generator.GenerateCharacterGroups(); - }; - - var start = DateTime.Now; - worker.RunWorkerAsync(); - worker.CancelAsync(); - - while (worker.IsBusy) - { - Assert.IsTrue(DateTime.Now.Subtract(start).Seconds < 6, "Failed to cancel within timeout (6 seconds)"); - Thread.Sleep(100); - } + CharacterGroupGenerator generator = new CharacterGroupGenerator(m_testProject); + var source = new CancellationTokenSource(); + source.CancelAfter(TimeSpan.FromMilliseconds(200)); + await Task.Run(() => { generator.GenerateCharacterGroups(source.Token); }, source.Token); Assert.Null(generator.GeneratedGroups); Assert.AreEqual(1, m_testProject.CharacterGroupList.CharacterGroups.Count);