I have a Winforms application where I am trying to print a pdf document which has multiple layers on it.
But the problem is, This all operation are running on UI thread and it is hanging the UI(not responding) for long time.
I know, this is happening because of UI thread is blocked so, I have tried to make this operation asynchronous by the help of powerful async/await keyword but still my long running method is not being asynchronous. It is not coming forward from the await tasks and still opearation is taking the same time as like synchronous operation.
What I tried:
Please see below:
/// <summary>
/// Show Print Dialog
/// </summary>
private void ShowPrintDialog()
{
// Initialize print dialog
System.Windows.Controls.PrintDialog prtDialog = new System.Windows.Controls.PrintDialog();
prtDialog.PageRangeSelection = PageRangeSelection.AllPages;
prtDialog.UserPageRangeEnabled = false;
_printOptions.PrintQueue = null;
_printOptions.PrintTicket = null;
Enabled = false;
// if there is a default printer then set it
string defaulPrinter = prtDialog.PrintQueue == null ? string.Empty : prtDialog.PrintQueue.FullName;
// Display the dialog. This returns true if the user selects the Print button.
if (prtDialog.ShowDialog() == true)
{
_printOptions.PrintQueue = prtDialog.PrintQueue;
_printOptions.PrintTicket = prtDialog.PrintTicket;
_printOptions.UseDefaultPrinter = (defaulPrinter == prtDialog.PrintQueue.FullName);
}
// Re-enable the form
Enabled = true;
}
/// <summary>
/// Event raised when user clicks Print
/// </summary>
/// <param name="sender">Source of the event</param>
/// <param name="e">Event specific arguments</param>
private void cmdOk_Click(object sender, EventArgs e)
{
ShowPrintDialog();
if (_printOptions.PrintTicket != null)
{
//Set search Options
_print.ExportDataItem = true;
_print.FileName = SearchTemplateName;
//shows progress bar form.
using (frmPrintSearchResultsProgress frmProgress =
new frmPrintSearchResultsProgress(_print, this, _printOptions))
{
frmProgress.ShowDialog(this);
}
if (_print.ExportDataItem && !_print.DataItemExported && !_print.CancelExport)
{
MessageBox.Show("No Document printed.");
}
}
//Store selected options for current user
SaveOptions();
if (!SkipExport)
Close();
}
/// <summary>
/// Event raised when progress form is shown.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void frmExportSearchResultsProgress_Shown(object sender, EventArgs e)
{
try
{
Application.DoEvents();
dispatcher = Dispatcher.CurrentDispatcher;
// record export/print job start time
_startedUtc = DateTime.UtcNow;
_print.WritingToPdfIndicator = lblWritingPdfFile;
lblProgress.Text = Properties.Resources.PrintSearchResults;
await dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(DoDataItemPrint));
}
}
/// <summary>
/// Prints the selected data items.
/// </summary>
private void DoDataItemPrint()
{
// LONG RUNNING OPERATIONS..
// THIS OPERATION IS BLOCKING THE UI.
}
So, as per mentioned in above code when I opened the PringDialogForm then it is opening a Progress Bar form to see the progress of printing the document and from here frmExportSearchResultsProgress_Shown() event is fired and inside it, I am calling the DoDataItemPrint() method which is time consuming.
So, I tried to make frmExportSearchResultsProgress_Shown event as async/await but still operation is taking the same time as previous.
Can anyone please suggest me where I am doing wrong?
your frmExportSearchResultsProgress_Shown method starts on the UI thread
it then dispatches DoDataItemPrint to the ... same UI thread
it schedules a continuation (via await) so that when that incomplete thing happens, we get back into frmExportSearchResultsProgress_Shown, and since there's probably a sync-context in play here, the sync-context capture (implicit in await) would push us to ... the UI thread
As you can see: everything is happening on the UI thread.
If you want to not block the UI, you need to get off the UI thread. That could be as simple as using Task.Run to invoke DoDataItemPrint, but without knowing what that code contains, it is impossible to know whether you're using thread-bound controls to do the printing. If you are... it will be hard to get away from that.
Related
I have a UnityEditor script where I have long running processes (File.ReadAllText() and XMLNode.SelectSingleNode()) that start when a button is clicked. I just want to inform the user to be patient. The problem is that the time consuming processing starts immediately when the button is clicked. The GUI doesn't do a redraw where I can put my message to the user.
I currently solve it for me by simply running a coroutine multiple times to be sure the message is drawn. But that looks cumbersome to me. Does anyone know better?
Thank you awesome people from SO :)
int fileProcessingEnumerator = 0;
// called on every redraw of my editor
void OnGUI()
{
DisplayRunButton();
}
/// <summary>
/// Draw a GUI button and handle the click
/// </summary>
private void DisplayRunButton()
{
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("<< GO! >>"))
{
fileProcessingEnumerator = 1;
EditorCoroutineUtility.StartCoroutine(ProcessFiles(), this);
}
if (fileProcessingEnumerator > 0)
EditorGUILayout.LabelField("Importprocess is running, please be patient..");
EditorGUILayout.EndHorizontal();
}
/// <summary>
/// Do the import of the files
/// </summary>
private IEnumerator ProcessFiles()
{
fileProcessingEnumerator++;
if (fileProcessingEnumerator < 3)
yield return null;
apDocument = new ApDocument(File.ReadAllText(apFilePath));
cDocument = new CDocument(File.ReadAllText(cFilePath)); //very expensive function
fileProcessingEnumerator = 0;
}
So as said you should make the loading asynchronous. Note that a Coroutine, in playmode or in the Editor, is never asynchronous. All that it allows is to yield which basically does
"Pause this routine, render this frame, continue this routine from here in the frame"
Everything between two yield statements is still executed in the Unity main thread.
Now there are probably many valid ways of how to do that exactly.
One would be to use an async Task and a main thread dispatcher pattern. Have to say that I'm not an expert for async - await so not totally sure it is done that way or if there is a more elegant solution ;)
// These are thread safe queues for handling loaded files back in the Unity thread
private readonly ConcurrentQueue<ApDocument> apDocs = new ConcurrentQueue<ApDocuemnt>();
private readonly ConcurrentQueue<cDocument> cDocs = new ConcurrentQueue<cDocuemnt>();
// Check if the routine is running
private bool busy;
// called on every redraw of my editor
void OnGUI()
{
DisplayRunButton();
}
/// <summary>
/// Draw a GUI button and handle the click
/// </summary>
private void DisplayRunButton()
{
EditorGUILayout.BeginHorizontal();
{
// Disable the button during the loading
EditorGUI.BeginDisabledGroup(busy);
{
if (GUILayout.Button("<< GO! >>"))
{
EditorCoroutineUtility.StartCoroutine(ProcessFiles(), this);
}
}
EditorGUI.EndDisabledGroup();
if(busy)
{
EditorGUILayout.LabelField("Importprocess is running, please be patient..");
}
}
EditorGUILayout.EndHorizontal();
}
private IEnumerator ProcessFilesRoutine()
{
// Set the busy flag for the drawer
busy = true;
// Start the async task
var loadingTask = Task.Run(ProcessFiles);
// Wait until the async task is done without freezing
yield return new WaitUntil(() => loadingTask.IsCompleted);
// handle the results
while(apDocs.TryDequeue(out var apDoc))
{
apDocument = apDoc;
}
while(cDocs.TryDequeue(out var cDoc))
{
cDocument = cDoc;
}
// Release the flag
busy = false;
}
// Do long stuff in a Task using async - await
private async Task ProcessFiles()
{
// Async file reading
var apContent = await File.ReadAllTextAsync(apFilePath);
var apDoc = new ApDocument(apContent);
var cContent = await File.ReadAllText(cFilePath);
var cDoc = new CDocument();
// Enqueue the results into the thread safe queuea
apDocs.Enqueue(apDoc);
cDocs.Enqueu(cDoc);
}
Note: Typed on smartphone but I hope the idea gets clear
I'm developing a VSTO add-in for Microsoft Word and I'm having difficulty with handling the BeforeSave event. I wish to edit the document each time the user saves but the BeforeSave event appears to be triggering in a certain scenarios when the user is not saving the document.
Assuming a user opens a blank Word document, enters some text and then tries to close the document. The dialog
displays. If the user clicks "Don't Save", the BeforeSave event still triggers (the event only triggers after the Save Changes dialog closes). Is there anyway to detect the difference between the use clicking Don't Save and clicking save or else to prevent the Before Save event from triggering in this scenario?
The event handler is being assigned using Word.ApplicationEvents4_DocumentBeforeSaveEventHandler
and the event handler signature is
Application_DocumentBeforeSave(Word.Document doc, ref bool saveAsUI, ref bool cancel)
Any help would be great thanks
The Word object model doesn't provide any event property for that. The best what you can do in that case are listed below:
If the document has never been saved before, you may check out the Document.Saved property.
Cancel the default action in the Application.DocumentBeforeSave event handler by setting the Cancel parameter to true. So, you can show your own dialog which mimics the default built-in one where you may handle every action made by the user.
The solution that worked for me in the end is based on the article at https://theofficecontext.com/2013/04/26/updated-word-after-save-event/
For reference, I'm working with Office 2019 and below is the code that worked for me - a slightly simplified version to filter out the user clicking Don't Save and trigger a Post Save event.
using System.Threading;
using Word = Microsoft.Office.Interop.Word;
/// <summary>
/// The word save handler.
/// </summary>
public class WordSaveHandler
{
public delegate void AfterSaveDelegate(Word.Document doc, bool isClosed);
// public events
public event AfterSaveDelegate AfterSaveEvent;
// module level
private bool preserveBackgroundSave;
private Word.Application oWord;
private string closedFilename = string.Empty;
/// <summary>
/// Initializes a new instance of the <see cref="WordSaveHandler"/> class.
/// CONSTRUCTOR takes the Word application object to link to.
/// </summary>
/// <param name="oApp">Word Application.</param>
public WordSaveHandler(Word.Application oApp)
{
this.oWord = oApp;
// hook to before save
this.oWord.DocumentBeforeSave += this.OWord_DocumentBeforeSave;
this.oWord.WindowDeactivate += this.OWord_WindowDeactivate;
}
/// <summary>
/// Gets public property to get the name of the file
/// that was closed and saved.
/// </summary>
public string ClosedFilename
{
get
{
return this.closedFilename;
}
}
/// <summary>
/// WORD EVENT fires before a save event.
/// </summary>
/// <param name="doc"></param>
/// <param name="saveAsUI"></param>
/// <param name="cancel"></param>
private void OWord_DocumentBeforeSave(Word.Document doc, ref bool saveAsUI, ref bool cancel)
{
// This could mean one of four things:
// 1) we have the user clicking the save button
// 2) Another add-in or process firing a resular Document.Save()
// 3) A Save As from the user so the dialog came up
// 4) Or an Auto-Save event
// so, we will start off by first:
// 1) Grabbing the current background save flag. We want to force
// the save into the background so that Word will behave
// asyncronously. Typically, this feature is on by default,
// but we do not want to make any assumptions or this code
// will fail.
// 2) Next, we fire off a thread that will keep checking the
// BackgroundSaveStatus of Word. And when that flag is OFF
// no know we are AFTER the save event
this.preserveBackgroundSave = this.oWord.Options.BackgroundSave;
this.oWord.Options.BackgroundSave = true;
// kick off a thread and pass in the document object
bool uiSave = saveAsUI; // have to do this because the bool from Word is passed to us as ByRef
new Thread(() =>
{
this.Handle_WaitForAfterSave(doc, uiSave);
}).Start();
}
/// <summary>
/// This method is the thread call that waits for the same to compelte.
/// The way we detect the After Save event is to essentially enter into
/// a loop where we keep checking the background save status. If the
/// status changes we know the save is complete and we finish up by
/// determineing which type of save it was:
/// 1) UI
/// 2) Regular
/// 3) AutoSave.
/// </summary>
/// <param name="doc">Document being saved.</param>
/// <param name="uiSave">Whether a SaveAs UI is displayed.</param>
private void Handle_WaitForAfterSave(Word.Document doc, bool uiSave)
{
bool docSaved = false;
try
{
// we have a UI save, so we need to get stuck
// here until the user gets rid of the SaveAs dialog
if (uiSave)
{
while (this.IsBusy())
{
Thread.Sleep(1);
}
}
docSaved = doc.Saved;
// check to see if still saving in the background
// we will hang here until this changes.
while (this.oWord.BackgroundSavingStatus > 0)
{
Thread.Sleep(1);
}
}
catch (ThreadAbortException)
{
// we will get a thread abort exception when Word
// is in the process of closing, so we will
// check to see if we were in a UI situation
// or not
if (uiSave)
{
this.AfterSaveEvent(null, true);
return;
}
else
{
// new, close, don't save - docSaved = FALSE
// open close don't save - docSaved = FALSE
// open close save - docSaved = TRUE
if (docSaved)
{
this.AfterSaveEvent(null, true);
}
return;
}
}
catch
{
this.oWord.Options.BackgroundSave = this.preserveBackgroundSave;
return; // swallow the exception
}
try
{
// if it is a UI save, the Save As dialog was shown
// so we fire the after ui save event
if (uiSave)
{
// we need to check to see if the document is
// saved, because of the user clicked cancel
// we do not want to fire this event
try
{
if (doc.Saved == true)
{
this.AfterSaveEvent(doc, false);
// new, save
// new, save as
// open save as
// open, turn on autosave
// new, turn on autosave
}
}
catch
{
// DOC is null or invalid. This occurs because the doc
// was closed. So we return doc closed and null as the
// document
this.AfterSaveEvent(null, true);
// -- new, close, save
}
}
else
{
// if the document is still dirty
// then we know an AutoSave happened
try
{
this.AfterSaveEvent(doc, false); // fire regular save event
// open, save
// open, autosave
}
catch
{
// DOC is closed
this.AfterSaveEvent(null, true);
}
}
}
catch { }
finally
{
// reset and exit thread
this.oWord.Options.BackgroundSave = this.preserveBackgroundSave;
}
}
/// <summary>
/// WORD EVENT – Window Deactivate
/// Fires just before we close the document and it
/// is the last moment to get the filename.
/// </summary>
/// <param name="doc"></param>
/// <param name="wn"></param>
private void OWord_WindowDeactivate(Word.Document doc, Word.Window wn)
{
this.closedFilename = doc.FullName;
}
/// <summary>
/// Determines if Word is busy essentially that the File Save
/// dialog is currently open
/// </summary>
/// <param name="oApp"></param>
/// <returns></returns>
private bool IsBusy()
{
try
{
// if we try to access the application property while
// Word has a dialog open, we will fail
object o = this.oWord.ActiveDocument.Application;
return false; // not busy
}
catch
{
// so, Word is busy and we return true
return true;
}
}
}
There is documentation with definitions on what each enum does. But how am I able to demo/see this in practice? And how can I possibly know when to use which priority?
Here's some code I have created in attempt to see how the priorty affects the ordering, and it provides me with proof that the ordering is correct (the first loop iteration will have added a SystemIdle enum to the dispatch queue), but it still got added to the string last
private void btn_Click(object sender, RoutedEventArgs e)
{
StringBuilder result = new StringBuilder();
new Thread(() =>
{
var vals = Enum.GetValues(typeof(DispatcherPriority)).Cast<DispatcherPriority>().Where(y => y >= 0).ToList();
vals.Reverse();
vals.ForEach(x =>
{
Dispatcher.BeginInvoke(new Action(() =>
{
result.AppendLine(string.Format("Priority: {0} Enum:{1}", ((int)x), x.ToString()));
}), x);
});
}).Start();
ShowResultAsync(result, 2000);
}
private async void ShowResultAsync(StringBuilder s, int delay)
{
await Task.Delay(delay);
MessageBox.Show(s.ToString());
}
and the output order stays the same, even when the list is reversed (added this line just after vals gets assigned):
vals.Reverse();
So once again, is there anything more I can use when determining which dispatch priority I should assign?
In the Prism Framework the DefaultDispatcher which wraps Dispatcher uses a Normal priority. This should be the bread-and-butter for nearly all application scenarios.
/// <summary>
/// Wraps the Application Dispatcher.
/// </summary>
public class DefaultDispatcher : IDispatcherFacade
{
/// <summary>
/// Forwards the BeginInvoke to the current application's <see cref="Dispatcher"/>.
/// </summary>
/// <param name="method">Method to be invoked.</param>
/// <param name="arg">Arguments to pass to the invoked method.</param>
public void BeginInvoke(Delegate method, object arg)
{
if (Application.Current != null)
{
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, method, arg);
}
}
}
As long as you are not running any actual logic on the UI thread I would recommend doing this.
If you did for some reason want to run "quick" logic on the UI thread you could follow the advice here and stick with a value of Background.
I did look into it a little and I found some usages in NuGet's source where they use Send, Normal, Background and ApplicationIdle for various reasons but in my WPF development I have never had to fine tune usage of DispatcherPriority to this degree.
With this code:
using (pbDialog = new pbDialogs())
{
ProgressBar = new frmProgress(this, false);
ProgressBar.SetProgressLabelText("Inventory Data");
MessageBox.Show("Set progress label text to Inventory data");
typeProgress = (int) ProgressStates.ProgressQRY;
ProgressBar.label1.Text += " (Receiving)";
if (pbDialog != null)
{
MessageBox.Show("pbDialog is not null");
//pbDialog.ShowDialog(ProgressBar, this);
}
else
{
MessageBox.Show("pbDialog IS null");
ProgressBar.ShowDialog();
}
ProgressBar = null;
MessageBox.Show("Made it to compressDB()");
compressDB();
. . .
}
I see "Set progress label text to Inventory data"
then "pbDialog is not null"
then "Made it to compressDB()"
Nothing too odd there; but if I uncomment the line that is commented above, I see only "pbDialog is not null"
It is hanging for some reason as a result to the call to ShowDialog(); what is really strange, though, is that this prevents "Set progress label text to Inventory data" from displaying. Why would that be the case?
Note: I think the "pb" in the code stands for "peanut brittle" or some such; I'm pretty sure about the "brittle" part, anyway.
UPDATE
Yeah, the use of ShowDialog() with pbDialog is one of scads of examples that the original coder was practicing job security by obscurity - but then he [un]fortunately skedaddled, leaving in his wake a cesspool of spaghetti/eggshell code with no comments, misleading names and every sort of bizarre and convoluted, counterintuitive practice imaginable in the witches brew he purportedly considered a masterpiece of elegant design and clever-clever tricks.
pbDialog is an instance of a class (pbDialogs). Just to give you a taste of how macabre and convoluted and tangled it all is, here is that class:
public class pbDialogs : IDisposable
{
private static Form m_top;
public pbDialogs()
{
} // pbDialogs Constructor
public static void Activate( Form form )
{
form.Capture = true;
IntPtr hwnd = OpenNETCF.Win32.Win32Window.GetCapture();
form.Capture = false;
OpenNETCF.Win32.Win32Window.SetForegroundWindow( hwnd );
} // Activate
/// <summary>
/// This method makes ShowDialog work the way I want, I think.
/// </summary>
/// <remarks>
/// Here is what it does:
/// 1. Sets the caption of the new window to the same as the caller's.
/// 2. Clears the caption of the parent so it won't show up in the task list.
/// 3. When the ShowDialog call returns, brings the previous window
/// back to the foreground.
/// </remarks>
/// <param name="dialog"></param>
/// <param name="parent"></param>
public void ShowDialog( Form dialog, Control parent )
{
Control top = parent.TopLevelControl;
string caption = top.Text;
dialog.Text = caption;
top.Text = "--pending--"; // Don't show parent in task list
dialog.Activated += new EventHandler( form_Activated );
dialog.Closed += new EventHandler( form_Closed );
m_top = dialog; // New top-most form
dialog.ShowDialog();
m_top = (Form)top; // The top dialog just changed
dialog.Activated -= new EventHandler( form_Activated );
dialog.Closed -= new EventHandler( form_Closed );
top.Text = caption; // Make visible in task list again
Activate( (Form)top ); // And make it the active window
} // ShowDialog
/// <summary>
/// If one of our other windows, such as the main window,
/// receives an activate event, it will activate the current
/// top-most window instead.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void form_Activated( object sender, EventArgs e )
{
if( (m_top != null) && !(sender == m_top) ) // Is this the top-most window?
Activate( m_top ); // No, activate the top dialog
} // form_Activated
private static void form_Closed( object sender, EventArgs e )
{
m_top = null; // When you close the top dialog, it's not top anymore
} // form_Closed
#region IDisposable Members
public void Dispose()
{
// TODO: Add pbDialogs.Dispose implementation
}
#endregion
} // class pbDialogs
There is also a "related" ProgressBar -- a form which shares a file with pbDialogs, and whose instance variable is defined in the file that contains the code above thusly:
public static frmProgress ProgressBar;
This is definitely "whack-a-mole" code - if I make one small, seemingly innocuous change, all Dallas breaks loose in what even a semi-sane person would consider a completely unrelated part of the code.
This may be an indication of just how squirrelly this code/project is: I will make a new build after commenting out a couple of lines, and the size of the file will change from 400KB to 408, or from 412 to 408, etc. It's not normal behavior for an .exe to change that much in size (in a relative sense) with such a small change, is it?
UPATE 2
This, in frmProgress (which has both "public class frmProgress : System.Windows.Forms.Form" and "public class pbDialogs : IDisposable") scares me:
using System.Windows.Forms;
using OpenNETCF.Windows.Forms;
The second (OpenNETCF) is grayed out, indicating it's not really used, but it may be that it was previously used, and somehow that "Windows.Forms" code inadvertently got switched to "System" code, and that may be contributing to its current groundsquirellyness.
ShowDialog is generally a blocking call. The code will not continue past this until the dialog is closed.
I need some help on showing Progress bar upon loading data on my DataGridView. Any sample code?
Thanks.
regards,
kurt
Start the fetching of the data on another thread in order to not lock up the UI thread. You could expose a method on the UI (presume you have separation of layers here) and call that method from your business layer. Windows Form controls expose a InvokeRequired flag that you can use to check if you are calling the control from the correct thread. If you are not on the right thread you can call a delegate to do so.
/// <summary>
/// Delegate to notify UI thread of worker thread progress.
/// </summary>
/// <param name="total">The total to be downloaded.</param>
/// <param name="downloaded">The amount already downloaded.</param>
public delegate void UpdateProgressDelegate(int total, int downloaded);
/// <summary>
/// Updates the progress in a thread-safe manner.
/// </summary>
/// <param name="total">The total.</param>
/// <param name="downloaded">The downloaded.</param>
public void UpdateProgress(int total, int downloaded)
{
// Check we are on the right thread.
if (!this.InvokeRequired)
{
this.ProgressBar.Maximum = total;
this.ProgressBar.Value = downloaded;
}
else
{
if (this != null)
{
UpdateProgressDelegate updateProgress = new UpdateProgressDelegate(this.UpdateProgress);
// Executes a delegate on the thread that owns the control's underlying window handle.
this.Invoke(updateProgress, new object[] { total, downloaded });
}
}
}
Or you could just use a BackgroundWoker ;)