my application calls crystal reports viewer to display a report. I run the report viewer in a separate thread. It works just fine. it displays the report properly. my problem is i want to kill the report while it is processing if it is taking too long to run. While the report is processing the busy indicator is spinning and it seems to block any UI on the report viewer form. My report viewer form has a crystal reports viewer on it along with a close button at the bottom of the form itself. i would like to be able to click the close button and have it stop the processing. Here is my code to run the viewer in a single apartment thread
public void RunReportStep1(UAReport report)
{
UAReportService service = new UAReportService(report);
service.RunReport();
var reportDocument = service.ReportDocument;
Thread staThread = new Thread(r => { RunReportStep2((ReportDocument) r); });
staThread.SetApartmentState(ApartmentState.STA);
staThread.Start(reportDocument);
staThread.Join();
}
public void RunReportStep2(ReportDocument reportDocument)
{
ReportViewerForm form = new ReportViewerForm(reportDocument);
form.BringToFront();
form.ShowDialog();
if (form.DialogResult == DialogResult.OK)
{
}
what is the best way to kill the thread from within the report viewer form while the processing is going on. Once the processing completes and the report is destroyed closing the report is no problem. It's only a problem while the processing is going on before the report is displayed. While the report is processing the close button is not responsive. sometimes if i click it repeatedly i can get a response and the report cancels. but it is not consistent and i have to click it repeatedly. that is not acceptable for my clients to have to do.
Call the service.RunReport() from another thread and wait for that thread to finish or for a certain amount of time to pass. I didn't write all the code for this, but anything I didn't write I a least described.
// Global so it can be reached from both threads
UAReportService service;
// Global variable that is written to when the report doc is ready:
ReportDocType reportDoc; //Can't use var here unfortunately
public void RunReportStep1(UAReport report)
{
service = new UAReportService(report);
Thread staThread = new Thread(r => { RunReportStep2((UAReportService)r); });
staThread.SetApartmentState(ApartmentState.STA);
staThread.Start(service);
// Save time the thread started:
DateTime start = DateTime.Now;
// Display a loading box (label or something, up to you).
// Then this function will end and your user can do stuff in the UI again (like press Cancel).
myLoadingBox.Visible = true;
}
public void RunReportStep2(UAReportService service)
{
service.RunReport();
reportDocument = service.ReportDocument;
}
// Call this new function periodically to see if the service thread finished, maybe once every second.
public checkAndDisplay()
{
// If thread is finished or 30 seconds have passed.
if (staThread.IsAlive == false || (DateTime.Now - start).TotalSeconds > 30)
{
// Show a message saying report failed to run
}
else // Else you can now show your report viewer
{
ReportViewerForm form = new ReportViewerForm(reportDocument);
form.BringToFront();
form.ShowDialog();
if (form.DialogResult == DialogResult.OK)
{
}
}
}
// Code for cancelling the report service call if user presses cancel button.
private void CancelButton_Click(object sender, EventArgs e)
{
// Terminate the report service thread.
staThread.Abort();
// Abort() isn't the nicest way to do this so if you can set a timeout on the UAReportService object (where it quits running after so long) that would be better.
// Or have it periodically check a variable to see if it should quit (see link below)
}
MSDN example of gracefully closing a thread.
Related
Is there any way that I can continue to the next function below (within the same function class) without closing the current m_frmLotEntry?
void ShowLotEntry()
{
DialogResult _dlgRet = DialogResult.None;
_dlgRet = m_frmLotEntry.ShowDialog(this);
// Next function here
}
Updates :
I managed to proceed with the code by changing the codes to below.
void ShowLotEntry()
{
DialogResult _dlgRet = DialogResult.None;
BeginInvoke(new System.Action(() => m_frmLotEntry.ShowDialog()));
// Next function here
}
The programs now proceed with the next step. However, I have another issue which is the next next function requires some data from the users key-in in the previous windows form. Therefore, is there any possible way that I can do to stop halfway through the next process?
void ShowLotEntry()
{
DialogResult _dlgRet = DialogResult.None;
BeginInvoke(new System.Action(() => m_frmLotEntry.ShowDialog()));
// Proceed with this function here.
// Stop before the function below.
if (_dlgRet == DialogResult.OK)
{
// Some codes here
}
Thanks.
ShowDialog is blocking operation and waits the return. You can start thread/task before showing the dialog and that would run "behind the scene" even though that the dialog is open. What about Show() would that be enough?
Check this answer for more details:
Async ShowDialog
I'm having an issue closing a program when there is a large amount of dialogs open. I was able to reproduce the bug using the following functions. The parent form on which this runs on is created using Application.Run(mainform).
When button1 is clicked, it creates a very basic form that opens another dialog of itself when "ok" is pressed. It then initiates the OnShutdown() function. This waits 15 seconds, then attempts to close all windows and exit the program. When more than 6 dummy windows are open, the function closes all the dummy windows but not the main window.
The steps for me reproducing the problem is:
Click button1
Close created heavy dialog
Click on on the msgbox
Click on button1 again
On the new heavy dialog, I press Ok to open another heavy dialog
Continue to open more heavy dialog, until 6-10 are open concurrently
Once the 15 seconds are up, all the dialogs close, but the parent form remains open, when it should be closed.
private void button1_Click(object sender,EventArgs e) {
BasicTemplate heavy = new BasicTemplate();
heavy.ShowDialog();
OnShutdown();
}
private void OnShutdown() {
timerSignals.Enabled=false;//quit receiving signals.
string msg = "";
msg+=Process.GetCurrentProcess().ProcessName+" ";
msg+=Lan.g(this,"will shut down in 15 seconds. Quickly click OK on any open windows with unsaved data.");
MsgBoxCopyPaste msgbox = new MsgBoxCopyPaste(msg);
msgbox.Size=new Size(300,300);
msgbox.TopMost=true;
msgbox.Show();
ODThread killThread = new ODThread((o) => {
Thread.Sleep(15000);//15 seconds
//Also happens with BeginInvoke()
Invoke((Action)(() => { CloseOpenForms(true); }));
Thread.Sleep(1000);//1 second
Invoke((Action)Application.Exit);
});
killThread.Start(true);
return;
}
public void Start(bool isAutoCleanup) {
_isAutoCleanup=isAutoCleanup;
if(_thread.IsAlive) {
return;//The thread is already running.
}
if(_hasQuit) {
return;//The thread has finished.
}
_dateTimeStart=DateTime.Now;
_thread.Start();
}
//Code for "OK" button on the heavy dialog created above
private void butOK_Click(object sender,EventArgs e) {
BasicTemplate formp = new BasicTemplate();
formp.ShowDialog();
}
private bool CloseOpenForms(bool isForceClose) {
for(int f=Application.OpenForms.Count-1;f>=0;f--) { //Count backwards to avoid out of bounds
if(Application.OpenForms[f]==this) {
continue;
}
Form openForm=Application.OpenForms[f];//Copy so we have a reference to it after we close it.
openForm.Hide();
if(isForceClose) {
openForm.Dispose();
}
else {
openForm.Close();//Attempt to close the form
if(openForm.IsDisposed==false) {
openForm.Show();//Show that form again
return false;
}
}
}
return true;
}
When line 3's "heavy.ShowDialog()" is changed to "heavy.Show()", the holdup no longer takes place. When multiple heavy dialogs are opened using a different button, the issue no longer takes place (up to ~20 new dialogs).
The shutdown sequence is run as a thread to allow the user to save any changes they have made before the program's database updates. I'm not sure if what I've implemented correctly shows the bug, but it at least produces similar events.
I've got a WinForms project that scans a given network and returns valid IP addresses. Once all the addresses are found, I create a user control for each and place it on the form. My functions to ping ip addresses use async and Task which I thought would "wait" to execute before doing something else, but it doesn't. My form shows up blank, then within 5 seconds, all the user controls appear on the form.
Declarations:
private List<string> networkComputers = new List<string>();
Here's the Form_Load event:
private async void MainForm_Load(object sender, EventArgs e)
{
//Load network computers.
await LoadNetworkComputers();
LoadWidgets();
}
The LoadNetworkComputers function is here:
private async Task LoadNetworkComputers()
{
try
{
if (SplashScreenManager.Default == null)
{
SplashScreenManager.ShowForm(this, typeof(LoadingForm), false, true, false);
SplashScreenManager.Default.SetWaitFormCaption("Finding computers");
}
else
Utilities.SetSplashFormText(SplashForm.SplashScreenCommand.SetLabel, "Scanning network for computers. This may take several minutes...");
networkComputers = await GetNetworkComputers();
}
catch (Exception e)
{
MessageBox.Show(e.Message + Environment.NewLine + e.InnerException);
}
finally
{
//Close "loading" window.
SplashScreenManager.CloseForm(false);
}
}
And the last 2 functions:
private async Task<List<string>> GetNetworkComputers()
{
networkComputers.Clear();
List<string> ipAddresses = new List<string>();
List<string> computersFound = new List<string>();
for (int i = StartIPRange; i <= EndIPRange; i++)
ipAddresses.Add(IPBase + i.ToString());
List<PingReply> replies = await PingAsync(ipAddresses);
foreach(var reply in replies)
{
if (reply.Status == IPStatus.Success)
computersFound.Add(reply.Address.ToString());
}
return computersFound;
}
private async Task<List<PingReply>> PingAsync(List<string> theListOfIPs)
{
var tasks = theListOfIPs.Select(ip => new Ping().SendPingAsync(ip, 2000));
var results = await Task.WhenAll(tasks);
return results.ToList();
}
I'm really stuck on why the form is being displayed before the code in the MainForm_Load event finishes.
EDIT
I forgot to mention that in the LoadNetworkComputers it loads a splash form which lets the user know that the app is running. It's when the form shows up behind that, that I'm trying to avoid. Here's a screenshot (sensitive info has been blacked out):
The reason one would use async-await is to enable callers of functions to continue executing code whenever your function has to wait for something.
The nice thing is that this will keep your UI responsive, even if the awaitable function is not finished. For instance if you would have a button that would LoadNetworkComputers and LoadWidgets you would be glad that during this relatively long action your window would still be repainted.
Since you've defined your Mainform_Loadas async, you've expressed that you want your UI to continue without waiting for the result of LoadNetWorkComputers.
In this interview with Eric Lippert (search in the middle for async-await) async-await is compared with a a cook making dinner. Whenever the cook finds that he has to wait for the bread to toast, he starts looking around to see if he can do something else, and starts doing it. After a while when the bread is toasted he continues preparing the toasted bread.
By keeping the form-load async, your form is able to show itself, and even show an indication that the network computers are being loaded.
An even nicer method would be to create a simple startup-dialog that informs the operator that the program is busy loading network computers. The async form-load of this startup-dialog could do the action and close the form when finished.
public class MyStartupForm
{
public List<string> LoadedNetworkComputers {get; private set;}
private async OnFormLoad()
{
// start doing the things async.
// keep the UI responsive so it can inform the operator
var taskLoadComputers = LoadNetworkComputers();
var taskLoadWidgets = LoadWidgets();
// while loading the Computers and Widgets: inform the operator
// what the program is doing:
this.InformOperator();
// Now I have nothing to do, so let's await for both tasks to complete
await Task.WhenAll(new Task[] {taskLoadComputers, taskLoadWidgets});
// remember the result of loading the network computers:
this.LoadedNetworkComputers = taskLoadComputers.Result;
// Close myself; my creator will continue:
this.Close();
}
}
And your main form:
private void MainForm_Load(object sender, EventArgs e)
{
// show the startup form to load the network computers and the widgets
// while loading the operator is informed
// the form closes itself when done
using (var form = new MyStartupForm())
{
form.ShowDialog(this);
// fetch the loadedNetworkComputers from the form
var loadedNetworkComputers = form.LoadedNetworkComputers;
this.Process(loadedNetworkComputers);
}
}
Now while loading, instead of your mainform the StartupForm is shown while the items are loaded.. The operator is informed why the main form is not showing yet. As soon as loading is finished, the StartupForm closes itself and loading of the main form continues
My form shows up blank, then within 5 seconds, all the user controls appear on the form.
This is by design. When the UI framework asks your app to display a form, it must do so immediately.
To resolve this, you'll need to decide what you want your app to look like while the async work is going on, initialize to that state on startup, and then update the UI when the async work completes. Spinners and loading pages are a common choice.
Using .NET4, c#,
I have a grid on a winform. When I hit a process button, I need to perform a timely operation with a few steps on each row and then update the row with a result. This way the user can see progress while the next row is processing. If the user hits cancel or closes the form, I need to finish the process for the current row and then break or close form. Based on my research, here is what I came up with. I'm left with 2 issues.
Here's my code:
delegate void OperateGridMethodDelegate();
delegate void UpdateProgressDelegate(int rowIndex, string msg);
bool isCancelled = false;
private void ButtonOne_Click(object sender, EventArgs e)
{
isCancelled=false;
OperateGridMethodDelegate delegate = new OperateGridMethodDelegate(LongProcess);`
delegate.BeginInvoke(null,null);
}
private void ButtonTow_Click(object sender, EventArgs e)
{
MyButtonOne.Enabled=false;
isCancelled=true;
}
private void LongProcess()
{
foreach (DataRow row in dataTableOne.Rows)
{
//Do Lengthy Process
DisplayResult( rowIndex, "this is result of operation" );
if(isCancelled)
{
MyButtonOne.Enabled=true;
break;
}
}
}
private void DisplayResult(int rowIndex,string message)
{
if(myGrid.InvokeRequired == false)
{
myGrid.Rows[rowIndex].value = message;
}
else
{
UpdateProgressDelegate delegate = new UpdateProgressDelegate(DisplayResult);
myGrid.BeginInvoke(delegate, new object[] { rowIndex, message });
}
}
The 2 issues I'm left with are:
When the form is closing, I want to wait for the LongProcess (that was called by my OperateGridMethodDelegate) to finish the row it's up to and then cancel (same way I did with ButtonTwo), but I can't get this to work.
I notice a weird behavior when debugging and I'm not sure if this is a bug or correct, when a row is finished processing, the result does not get set. Instead, the next row is processed, and then the previous row result gets displayed. The second row result is displayed when the third row finishes etc. Then the last 2 row results get displayed basically at the same time.
Am I approaching this correctly?
If you've launched some task in a thread, to wait for it to finish before proceeding you can do:
yourThread.Join();
This will block until the thread exits. You may want to use something like this in your Form_Closing event
Alternatively, if you want the thread to automatically terminate if the application is terminated, you need to set the background property of the thread:
yourThread.IsBackground = true;
This means it is a background thread of your main (UI) thread, so it will end with the main thread.
Edit:
Using your own thread:
private Thread yourThread = new Thread(() = > LongProcess());
yourThread.IsBackground = true;
yourThread.Start(); // This will begin your 'LongProcess' function.
Now if you want to block somewhere and wait for this to complete, you can do what I mentioned first: yourThread.Join();
Also note that if you set IsBackground = false and someone closes the application, that thread will not exit immediately, it will continue until it is complete despite the UI window being closed.
I am having an issue with refresh for a normal text block. My code is as follows:
public Window1()
{
InitializeComponent();
Thread newt = new Thread(Tick);
newt.Start();
}
void Tick()
{
while (true)
{
textBlock1.Dispatcher.Invoke(
DispatcherPriority.Normal,
new Action(
delegate()
{
textBlock1.Text = DateTime.Now.ToString("h:mm:ss");
Thread.Sleep(1000);
}
));
}
}
With the application running, the monitor goes to sleep mode at 12:55:55. When I reactivate the monitor at 1:02, I noticed that the value is updated each second, however as the hour has no ten's unit now, it is left aligned and the unit's place of the seconds of 12:55:55 is not refreshed.
So, it shows the value as: 1:02:555. The last 5 is from the 12:55:55 before the monitor went to sleep.
The control is only refreshed after I minimize and maximize the window in which case it shows the time correctly properly refreshed.
It's a display driver issue with NVidia FX 1800. Has anyone ran into the same problem?
Set the dispatcher priority to a lower one, this will make sure your text box is refreshed.
Try to use textBox.InvalidateVisual(). It forces the textbox to rerender. But it has performance impacts as the control should know itself very exact what part should get rerendered
You are putting the UI thread to sleep for a 1 second before returning. You should not put the UI thread to sleep. I believe you intended for your background thread to sleep for 1 second, if so then you'd need to do:
void Tick()
{
while (true)
{
textBlock1.Dispatcher.Invoke(
DispatcherPriority.Normal,
new Action(
delegate()
{
textBlock1.Text = DateTime.Now.ToString("h:mm:ss");
}
));
// Executed on background thread, not UI thread
Thread.Sleep(1000);
}
}