I have an application which has an asynchronous operation built into it that loops over a large number of items and performs lengthy calculations on each item, as this occurs certain conditions will cause the application to pop up a modal dialog for user input. Interestingly, I noticed while running this today that in one application instance the modal dialogs somehow got the wrong parent form- rather than providing a modal block to my application the dialogs had taken a Digsby chat window as their parent.
The Digsby window that they became children of popped up around the same time as the first dialog was created (possibly the exact same time) and it would seem that they threads somehow crossed one another because the Digsby window became completely blocked until the async operation completed. I do not know how this is possible considering how per-process threading works, but it certainly did happen. What would cause such a bug? The application is C# .NET 3.5 with Windows Forms running on Windows XP, by the way.
Here's the async code in a nutshell:
Action<List<ClubListing>> a = delegate(List<ClubListing> list)
{
for (int i = 0; i < list.Count; i++)
{
var cl = list[i];
if (cl.MatchingClubListing == null)
{
var compare = CompareNames(cl.Club.Name);
if (compare.Any(c => c.Value == 0 && c.Key.Club.State == cl.Club.State))
{
var match = compare.First(c => c.Value == 0 && c.Key.Club.State == cl.Club.State);
compareDialog.ClubA = cl.Club;
compareDialog.ClubB = match.Key.Club;
DialogResult dr = compareDialog.ShowDialog();
if (dr == DialogResult.Yes)
{
cl.MatchingClubListing = match.Key;
match.Key.MatchingClubListing = cl;
}
else if (dr == DialogResult.Abort)
{
break;
}
}
}
this.Invoke(new MethodInvoker(delegate()
{
this.prbOperationProgress.Value = i;
}));
}
};
AsyncCallback callback = new AsyncCallback(SaveAndUpdate);
var result = a.BeginInvoke(this.lbxLiveClubs.Items.Cast<ClubListing>().ToList(), callback, null);
Add the parent dialog as argument to compareDialog.ShowDialog(). Eg:
Form parent = ...;
compareDialog.ShowDialog(parent);
Just as you're Invoking your ProgressBar update, you need to Invoke() the showing of your dialog.
It looks like you are showing your compare dialog in the worker thread. I think you may be okay if you use a call to Invoke to show your dialog.
Related
I am working on a project in C# that requires user input from a pop-up message box.
I am also trying to have my code perform a series of tasks while the message box is active.
My goal is to have the MessageBox describe the tasks being performed and ask the user to observe and verify that they are being carried out.
I want these tasks to continuously be performed until the user clicks on a response in the MessageBox.
To create my message box, I am using:
MessageBox.Show(string <message>, string <caption>, MessageBoxButton.YesNo)
And the basic structure of what I am trying to do is as follows:
var userInput = MessageBox.Show("Are tasks A, B, C running?", "winCaption", MessageBoxButton.YesNo);
while (// <No response to MessageBox>)
{
// Task A
// Task B
// Task C
if (userInput == MessageBoxResult.Yes)
{
// PASS
// exit while loop
}
else
{
// FAIL
// exit while loop
}
}
I have found that when the MessageBox.Show() occurs & the pop-up window appears, the code seems to hang at that line until the user response is detected.
Is it possible to make this work? If not, are there any alternatives?
Thank you
How about calling MessageBox on separate Thread?
var action = new Action(() =>
{
var userInput = MessageBox.Show("Are tasks A, B, C running?", "winCaption", MessageBoxButtons.YesNo);
if (userInput == DialogResult.Yes)
{
// PASS
}
else
{
// FAIL
}
});
new Thread(new ThreadStart(action)).Start();
MessageBox.Show creates a modal dialog, meaning that execution on the thread stops until it is closed. You'll need to create a new Form that displays instead of using the built in MessageBox.
Once you've created the form, call it like this:
MyForm form = new MyForm();
form.Show(); //Note that this will NOT be modal
Keep in mind that Form does have a way to display it modally called ShowDialog(). It can be a bit confusing, so I'll summarize here:
MessageBox.Show(); //Modal
Form.Show(); //Not Modal
Form.ShowDialog(); //Modal
My UI get frozen while I add each line of a text file in a List.
This is my current code :
private void LoadProxies_Click(object sender, EventArgs e)
{
OpenFileDialog dialog = new OpenFileDialog();
dialog.Title = "Select your Proxies file";
dialog.Filter = "Text File|*.txt";
DialogResult result = dialog.ShowDialog();
if (result == DialogResult.OK)
{
int list = proxiesList.Count;
Parallel.ForEach(File.ReadLines(file), line =>
{
if (line != null && line.Contains(":"))
{
proxiesList.Add(line);
list++;
InvokeUI(() => { Proxies.Text = list.ToString(); });
}
});
}
}
This is the InvokeUI method :
private void InvokeUI(Action a)
{
BeginInvoke(new MethodInvoker(a));
}
I tried using Parallel.ForEach(File.ReadLines(file), line => ... and await Task.Factory.StartNew(() => ... but it's not fixing my problem.
How can I solve this ? Thanks.
My UI get frozen while I add each line of a text file in a List
The problem with your code is that it is a rather tight loop and if the file is large the following line will be called a great deal of times per second:
BeginInvoke(new MethodInvoker(a));
This would result in the Windows Message Pump being flooded with update UI requests.
A better action is to either:
Since all you are doing is trying to display the numeric value of list on screen, consider updating say once a second by way of a Windows Forms timer rather than calling BeginInvoke
If you must update from a worker thread, don't BeginInvoke for each item, consider updating in batches. In this case perhaps update every 100
Alternatively you may want to consider TPL DataFlow
I have some code that when I call my CustomMessageBox it displays the box with a user prompt for an amount of my object to add, once that is done I have it added to a list of objects. Once added, it then Displays a MessageBox.Show to just let the user know it was added.
My problem is that when I run the code it executes all the code, bypasses the display of the Custom message box, then displays the MessageBox.Show, and THEN displays the CMB.Show. I ran the code through the debugger and followed the trail and it hits the CMB.Show before the MessageBox.Show, but is displayed once the code is done. Sorry, I am still learning and might not be telling the problem well, please let me know if there is anything I can further explain upon.
Some code:
private int BasicLand(Card basicLand)
{
var countBox = new TextBox
{
Name = "count",
Width = 100,
};
var cmbCount = new CustomMessageBox
{
Caption = "Blah",
Content = countBox,
RightButtonContent = "ok",
};
cmbCount.Dismissed += (s1, e1) =>
{
switch (e1.Result)
{
case CustomMessageBoxResult.RightButton:
if (int.TryParse(countBox.Text, out tempInt) && Convert.ToInt32(countBox.Text) > 0)
{
countReturn = Convert.ToInt32(tempInt);
break;
}
else
{
//Some code for error....
}
}
};
cmbCount.Show();
return countReturn;
}
Then the other part that triggers first but is last in the code block.
MessageBox.Show("Object was added to List!");
I tried adding the ShowDialog to the custom box but it came up broken in VS. BasicLand is called within another method and when the object is added to the list it will display the MessageBox.Show.
The problem with your code is, it does not take into account that any user interaction is asynchronous. When you call Show() it will actually show the messagebox, but it will not block your currently running thread, the other statements after the call to Show() will be executed immediately and thus your method returns a returnvalue that has not been provided by the user but is just the default. To fix this you have to write your code in continuations.
private void PromtUserForFeeblefezerAmount(Action<int> continueFeeblefzing, Action cancel)
{
var messagebox = CreateFeeblefezerPromt();
messagebox.Dismissed += (sender, args) =>
{
if ( args.Result == CustomMessageBoxResult.RightButton )
continueFeeblefzing( GetFeeblefezerAmount(messagebox) );
else
cancel();
};
messagebox.Show();
}
I have made a WPF application which has a button to move some files attached
to the column cells from one column to another column. The moment when I press
the button it shows a nice animation and moves all files to the next column's cells.
But my real problem is once I give my function color_check(),
my application is getting stuck. I really don't know why. Is there
any help I can get out for this?
Code:
private void button3_Click(object sender, EventArgs e)
{
Hide();
bool done = false;
ThreadPool.QueueUserWorkItem((x) =>
{
using (var splashForm = new Form4())
{
splashForm.Show();
while (!done)
Application.DoEvents();
splashForm.Close();
}
});
move(); //file moving function
//color_check(); if i give this fn, my form stucks and comes to live after 10 - 20 sec
done = true;
MessageBox.Show("TEST FINISHED");
Show();
}
public void color_check() //this is my problem making fn
{
dataGridView1.Refresh();
string strVal = ini.ReadValue("Action", "Doc-Controller");
bool authenticated = true;
if (authenticated == UserInCustomRole(strVal))
{
foreach (DataGridViewRow row in dataGridView1.Rows)
{
// Application.DoEvents();
string fName1 = System.IO.Path.GetFileNameWithoutExtension(row.Cells[3].Value.ToString());
string fName2 = System.IO.Path.GetFileNameWithoutExtension(row.Cells[4].Value.ToString());
if (!string.IsNullOrEmpty(fName1) && !string.IsNullOrEmpty(fName2))
{
var f1 = GetValue(fName1.ToCharArray()[fName1.Length - 2]) * 16 + GetValue(fName1.ToCharArray()[fName1.Length - 1]);
var f2 = GetValue(fName2.ToCharArray()[fName2.Length - 2]) * 16 + GetValue(fName2.ToCharArray()[fName2.Length - 1]);
//if (System.IO.Path.GetFileName(fName1) != System.IO.Path.GetFileName(fName2))
if (f1 > f2)
{
//MessageBox.Show(fName1);
DataGridViewCellStyle style = new DataGridViewCellStyle();
style.BackColor = Color.Yellow;
row.Cells[3].Style = style;
}
else if (f2 > f1)
{
//MessageBox.Show(fName1);
DataGridViewCellStyle style = new DataGridViewCellStyle();
style.BackColor = Color.Yellow;
row.Cells[4].Style = style;
}
if (f1 == f2)
{
DataGridViewCellStyle style = new DataGridViewCellStyle();
style.BackColor = Color.Plum;
row.Cells[4].Style = style;
row.Cells[3].Style = style;
}
}
}
}
The issue is that your code which is invoked on button3_click() is blocking the UI thread. That's why it appears to freeze for a while - the code is executing, and once it's complete the UI thread becomes responsive again.
The way to resolve this is to perform your actions asynchronously on another thread. In .NET 4 and beyond, you can use Tasks and the async/await keywords to help you manage this. If you are working on a version older than .NET 4, then you'll want to take a look at BackgroundWorker or other threading options compatible with your version of .NET.
Note that if you want to modify the GUI in your async method, you may need to use a Dispatcher.Invoke() to do so safely.
Here are some links to help you understand some approaches available to you
C# Blog on Understanding a simple async program
MSDN reference for async/await
Related StackOverflow question on how to use BackgroundWorkers
Related StackOverflow question on how to access the UI thread directly
In a normal UI application, no 2 functions that work on the GUI will run at the same time. This would result in a lot of issues otherwise, which usually would cause the program to crash. For example there could be 2 functions running at the same time that each check if the same list has at least one element and then remove an element - because they run at the same time they first both check if the list has 1 element.
That's why GUI functions all run in the same thread, meaning they run only one at a time. While color_check runs, other functions don't run.
You can launch additional threads and do work on them that is executed in parallel, of you can speed up the color_check function, for example by breaking it up into parts which run at lower priority one at a time, using a dispatcher
Take this:
public void color_check() //this is my problem making fn
{
dataGridView1.Refresh();
string strVal = ini.ReadValue("Action", "Doc-Controller");
bool authenticated = true;
if (authenticated == UserInCustomRole(strVal))
{
foreach (DataGridViewRow row in dataGridView1.Rows)
{
ProcessRow(row);
}
}
}
and change it to this:
public void color_check() //this is my problem making fn
{
dataGridView1.Refresh();
string strVal = ini.ReadValue("Action", "Doc-Controller");
bool authenticated = true;
if (authenticated == UserInCustomRole(strVal))
{
foreach (DataGridViewRow row in dataGridView1.Rows)
{
Dispatcher.BeginInvoke(DispatcherPriority.Background, ()=>{Process(row);});
}
}
}
In this code, Dispatcher.BeginInvoke tells the UI thread that it should run Process(row), as soon as it finds the time. This might result in 200 Process(row) calls that are waiting to be executed. It's still all executed on the UI thread and only one thing at a time. If a mouse click happens after the first hundred have been completed, the GUI thread will first finish number onehunderd and one, and then handle the mouseclick, before picking up the remaining calls to process.
There's a disadvantage to this approach. By allowing other functions to be executed in between different calls to Process(row), you may get surprising results. Especially if these other processes also change the cell styles.
I have the following code:
ShowPoup();
if (_watcher == null)
{
_watcher = new GeoCoordinateWatcher(GeoPositionAccuracy.High);
_watcher.MovementThreshold = 15; // use MovementThreshold to ignore noise in the signal
_watcher.StatusChanged += new EventHandler<GeoPositionStatusChangedEventArgs>(watcher_StatusChanged);
}
if (!_watcher.TryStart(true, TimeSpan.FromSeconds(3)))
{
MessageBox.Show("Please turn on location services on device under Settings.");
//HidePopup();
}
My problem is that the popup doesn't appear until after the _watcher.TryStart() method returns. The point of the popup is to show a loading overlay to tell the user the app is doing something. It's pointless to have it show after the work is done, at which point I hide the popup, so the user never sees anything.
I have this popup code throughout the app and this is the first time I've encountered this issue. Even if I call ShowPopup() in a separate method before calling the current method, it still doesn't show until after _watcher starts. I'm not sure why this is happening.
It looks like you are blocking the UI thread during the TryStart. If you can move the watcher initialization to a background thread (e.g. to the threadpool) then you can keep the display "alive".
Something like:
ShowPoup();
if (_watcher == null)
{
_watcher = new GeoCoordinateWatcher(GeoPositionAccuracy.High);
_watcher.MovementThreshold = 15; // use MovementThreshold to ignore noise in the signal
_watcher.StatusChanged += new EventHandler<GeoPositionStatusChangedEventArgs>(watcher_StatusChanged);
}
System.Threading.ThreadPool.QueueUserWorkItem((ignored) =>
{
if (!_watcher.TryStart(true, TimeSpan.FromSeconds(3)))
{
Dispatcher.BeginInvoke(() =>
{
HidePopup();
MessageBox.Show("Please turn on location services on device under Settings.");
}
});
});