public static void StartWorker(TagWriterWorkerArgs args) { if (worker.IsBusy) { Debug.LogError("Cannot start writing tags while already writing tags!"); } else { // Ensure the manualResetEvent is set (unpaused) Paused = false; mainForm.ShowProgress(true); worker.RunWorkerAsync(args); } }
// Called on the main thread when the worker is complete, or is cancelled private static void worker_Completed(object sender, RunWorkerCompletedEventArgs e) { // Hide the progress state for the taskbar icon TaskbarManager.Instance.SetProgressState(TaskbarProgressBarState.NoProgress); // Reset the "Processed" flag on all rows foreach (var entry in args.rows.Select(x => x.DataBoundItem).Cast <Entry>().Where(x => x.processed)) { entry.processed = false; } if (e.Cancelled) { Debug.Log("--- CANCELLED ---"); } // Check to see if the worker was cancelled. // If we weren't already removing/reverting tags, ask the user if they want to revert the changes that were made if (e.Cancelled && lastArgs != null && !lastArgs.removeTags) { if (MessageBox.Show("Do you want to revert changes made to files, and erase the playback statistics tags that have already been written to files?", "Revert", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes) { var args = lastArgs; args.removeTags = true; // Get subset of entries where the tags have already been written args.rows = args.rows.Where(x => ((Entry)x.DataBoundItem).wroteTags); // Start a new worker with the same args, but removeTags true // Takes into account skip arguments too StartWorker(args); return; } } // If any errors occurred, show a dialogue containing the errors if (entryErrors != null && entryErrors.Count > 0) { new TagWriterErrors(entryErrors).Show(); } args = lastArgs = null; Console.CursorVisible = true; mainForm.ShowProgress(false); }
private static void worker_DoWork(object sender, DoWorkEventArgs e) { args = (TagWriterWorkerArgs)e.Argument; // Assign args to static variable. This is mainly used for cancellation/reverting, to understand what files were targeted in the operation lastArgs = args; int processed = 0; int count = args.rows.Count(); int rowIndex = 0; // Misc int success = 0; // <- File was successfully written to int unmapped = 0; // <- Entry didn't have a file mapped to it int unsupportedFormat = 0; // <- File was of an unsupported format (UnsupportedFormatException) int corruptFile = 0; // <- File was corrupted (CorruptFileException) int ioError = 0; // <- Error occured while loading the file (IOException) int otherError = 0; // <- Another exception occurred Debug.Log(string.Format("\nWriting tags for {0} files, using the following parameters:\nfilter:\t\t{1}\nskipDateAdded:\t{3}\nskipLastPlayed:\t{4}\nskipPlayCount:\t{5}\nskipRating:\t{6}\nremoveTags:\t{7}\n\nUse Ctrl-C to terminate", count, args.filter, args.filterNot, args.skipDateAdded, args.skipLastPlayed, args.skipPlayCount, args.skipRating, args.removeTags)); // Exit here if we have nothing to process if (count == 0) { Debug.LogError("No entries to process!", true); return; } // Wait 3 seconds before starting if (Settings.Current.workerDelayStart) { for (int i = 0; i < 3; i++) { Console.Write("."); System.Threading.Thread.Sleep(1000); } } // A dictionary of entries and errors that have occured entryErrors = new ConcurrentDictionary <Entry, string>(); // Progress report counter, gets set to the sw.ElapsedMilliseconds at every report progress interval long progressReportCounter = 0; // Start a new stopwatch var sw = System.Diagnostics.Stopwatch.StartNew(); // Set the TaskbarProgressState TaskbarManager.Instance.SetProgressState(TaskbarProgressBarState.Normal); Console.Write("\n\n"); void ReportProgress() { // Report progress to main thread to update DataGridView selection worker.ReportProgress(0, new ProgressArgs() { processed = processed, count = count, timeMs = sw.ElapsedMilliseconds, currentRowIndex = rowIndex, }); } int lowestBreakIndex = 0; // Set up the ParallelOptions var parallelOptions = new ParallelOptions() { MaxDegreeOfParallelism = Settings.Current.maxParallelThreads }; Resume: // Split the work into multiple parallel threads ParallelLoopResult parallelLoopResult = Parallel.ForEach(args.rows.Skip(lowestBreakIndex), parallelOptions, (row, parallelLoopState) => { // Firstly, do some processing stuffs that can only be done in the worker // Check if we should pause if (isPaused) { // Breaking allows current parallel iterations to complete before stopping parallelLoopState.Break(); return; } // Check if we should cancel if (worker.CancellationPending) { e.Cancel = true; parallelLoopState.Stop(); return; } // Check if we should report progress // Only report progress every <progressReportInterval> milliseconds if (Settings.Current.workerReportsProgress && sw.ElapsedMilliseconds - progressReportCounter > Settings.Current.workerReportProgressInterval) { rowIndex = row.Index; progressReportCounter = sw.ElapsedMilliseconds; ReportProgress(); } // Fetch the DataBoundItem from the DataGridViewRow var entry = (Entry)row.DataBoundItem; // Check if the entry hasn't already been processed in this session // This may be the case if we paused previously if (!entry.processed) { // Create a new DebugStack // We're unable to log from a parallel for, as the logs would be out of sync - and wouldn't be clustered together // So instead of logging normally, logging within a Parallel is done to a DebugStack, which essentially just collects the logs so that we can fire them all at once // This is done here instead of in WriteTagsForRow, as that method has many exit points, therefore we would have to return a debugstack object from it anyway var ds = new DebugStack(); // Write the tags for this Entry var result = WriteTagsForRow(entry, ++processed, ds); // Increment the error/success counter switch (result) { case TagWriterReturnCode.Success: success++; break; case TagWriterReturnCode.Unmapped: unmapped++; break; case TagWriterReturnCode.UnsupportedFormat: unsupportedFormat++; break; case TagWriterReturnCode.CorruptFile: corruptFile++; break; case TagWriterReturnCode.IOEError: ioError++; break; case TagWriterReturnCode.OtherError: otherError++; break; } // Set the processed flag on this entry to true entry.processed = true; // Log the DebugStack ds.Push(); } }); // If the Parallel.ForEach was paused, isPaused will be set if (isPaused) { // Save the index of the lowest last item processed lowestBreakIndex = (int)parallelLoopResult.LowestBreakIteration; Debug.Log("--- PAUSED ---"); sw.Stop(); // Set the taskbar ProgressState to Paused TaskbarManager.Instance.SetProgressState(TaskbarProgressBarState.Paused); // Halt execution until unpaused (Reset) manualResetEvent.WaitOne(); // If we're here, the operation was unpaused // Handle cancelling while paused if (worker.CancellationPending || e.Cancel) { e.Cancel = true; return; } // Otherwise resume else { Debug.Log("--- RESUMED ---"); sw.Start(); TaskbarManager.Instance.SetProgressState(TaskbarProgressBarState.Normal); // Jump back up to Resume goto Resume; } } // Return if we cancelled if (e.Cancel) { return; } sw.Stop(); // Do final ReportProgress rowIndex = args.rows.Last().Index; ReportProgress(); string resultStr = string.Format("Took {0}ms ({1})\n {2} files were written to successfully\n {3} files could not be written to\n {4} unsupported format\n {5} corrupt\n {6} IO errors\n {7} unmapped\n {8} other errors occurred", sw.ElapsedMilliseconds, TimeSpan.FromMilliseconds(sw.ElapsedMilliseconds).ToString("m\\:ss"), success, unsupportedFormat + corruptFile + ioError + unmapped + otherError, unsupportedFormat, corruptFile, ioError, unmapped, otherError); Debug.Log("Done! " + resultStr); mainForm.Invoke(() => mainForm.Flash(false)); System.Media.SystemSounds.Exclamation.Play(); MessageBox.Show(resultStr, "Done!", MessageBoxButtons.OK, MessageBoxIcon.Information); if (args.removeTags) { Debug.Log("\n" + Consts.REMOVE_TAGS_FINISHED); } else { Debug.Log("\n" + Consts.WRITE_TAGS_FINISHED); } }