In a Windows Forms application, i have a ComboBox element and need to run some code pieces periodically when ComboBox element changes.
The problem with code is, when ComboBox Text changes A to B or vice versa ,event handler triggers and previous while(true) loops are still running.
When event handler is triggered, needs to run one infinite loop.
private void comboBox_SelectedIndexChanged(object sender, EventArgs e)
{
string status = comboBox.SelectedItem.ToString();
Task.Run(async () =>
{
while (true)
{
// Running on ui thread
datagrid.BeginInvoke((MethodInvoker)delegate
{
status = comboBox.SelectedItem.ToString();
});
if (status == "A")
{
Work();
}
else if (status == "B")
{
anotherWork();
}
else if (status == "C")
{
someWork();
break;
}
await Task.Delay(3000);
}
});
}
Tried with ManualResetEvent, CancellationTokenSource, and bool check but none of them solved the problem.
What is the best practice to prevent repeated while loops?
Edit: Implemented timer already but wanted to while loop trick. I was doing bool check in a wrong way, Owen s answer is accepted.
Add a bool to check whether or not the loop is running:
private bool _loopRunning;
private void comboBox_SelectedIndexChanged(object sender, EventArgs e)
{
if(_loopRunning)
{
return;
}
_loopRunning = true;
string status = comboBox.SelectedItem.ToString();
Task.Run(async () =>
{
// stuff
_loopRunning = false;
});
}
Related
My requirement is to insert item in a queue and process it but the items should be added first and after a while they should be processed (as some other things needs to be set before processing the items. Here is the coding I have done so far.
#region Variables Declarations
private Thread threadTask = null;
ConcurrentQueue<string> concurrentQueue = new ConcurrentQueue<string>();
string currentSeqNo;
string previousSeqNo = "-1";
#endregion
private void test1_Load(object sender, EventArgs e)
{
AddItems();
if (threadTask == null)
{
threadTask = new Thread(Kick);
Thread.Sleep(5000);
threadTask.Start();
}
}
private void AddItems()
{
for (Int64 i = 100000; i < 300000; i++)
{
concurrentQueue.Enqueue(i.ToString());
this.Invoke(new MethodInvoker(delegate()
{
label1.Text = i.ToString();
label1.Update();
}));
}
}
private void Kick()
{
while (true)
{
int recordCountNew = concurrentQueue.Count();
if (recordCountNew != 0)
{
RemoveItems();
}
}
}
private void RemoveItems()
{
string item;
while (concurrentQueue.TryDequeue(out item))
{
this.Invoke(new MethodInvoker(delegate()
{
label2.Text = item;
label2.Update();
}));
currentSeqNo = item; // second time does not start wil 100000
if (previousSeqNo != "-1")
{
if (long.Parse(currentSeqNo) != long.Parse(previousSeqNo) + 1)
{
Reconnect();
}
else
{
//Process item
previousSeqNo = currentSeqNo;
}
}
else
{
//Process item
previousSeqNo = currentSeqNo;
}
}
}
private void Reconnect()
{
currentSeqNo = "";
previousSeqNo = "-1";
string someItem;
while (concurrentQueue.Count > 0)
{
concurrentQueue.TryDequeue(out someItem);
}
this.Invoke(new MethodInvoker(delegate()
{
label1.Text = "";
label2.Text = "";
label1.Update();
label2.Update();
}));
AddItems();
if (threadTask == null)
{
threadTask = new Thread(Kick);
threadTask.Start();
}
}
private void button1_Click_1(object sender, EventArgs e)
{
Reconnect();
}
To reproduce the issue: Run the app and in the middle click on the button. Now the queue should again be started from 100000 but it shows the number somewhere greater than 100000.
Please advise how do I release all the resources to make a fresh start after clicking a button. Though I am setting them to default and also clearing the queue but it still shows the old values in currentSeqNo when 'RemoveItems' method is called.
What you see is a race condition between the Kick thread and the button click handler. When you press the button you execute Reconnect() in it you clean the queue and then call the AddItems() function. But all this time the Kick function tries to Dequeue and so you end up each time with an arbitrary amount of items in it. What you should do is to synchronize between these functions or prevent the Kick from executing while you are adding items.
Couple of comments:
1) You Kick() method have an infinite loop, that too without sleep. Every thread started will keep on running as you didn't have a scope for thread to come out.
You can have a member variable like bKeepRunning with default value as true. Set that variable to false in beginning of Reconnect() function. Something like:
private void Kick()
{
while (bKeepRunning)
{
int recordCountNew = concurrentQueue.Count();
if (recordCountNew != 0)
{
RemoveItems();
}
}
}
Why do you have Thread.Sleep(5000); in test1_Load()? I dont think that is needed.
I made small change in your code, something like:
private void AddItems()
{
for (Int64 i = 100000; i < 300000; i++)
{
concurrentQueue.Enqueue(i.ToString());
this.Invoke(new MethodInvoker(delegate()
{
label1.Text = i.ToString();
label1.Update();
}));
if (i < 100004)
Thread.Sleep(1000);
}
}
private void Kick()
{
while (bKeepRunning)
{
int recordCountNew = concurrentQueue.Count();
if (recordCountNew != 0)
{
RemoveItems();
}
}
}
private void Reconnect()
{
currentSeqNo = "";
previousSeqNo = "-1";
bKeepRunning = false;
threadTask = null;
string someItem;
while (concurrentQueue.Count > 0)
{
concurrentQueue.TryDequeue(out someItem);
}
this.Invoke(new MethodInvoker(delegate()
{
label1.Text = "";
label2.Text = "";
label1.Update();
label2.Update();
}));
Thread.Sleep(2000);
AddItems();
bKeepRunning = true;
if (threadTask == null)
{
threadTask = new Thread(Kick);
threadTask.Start();
}
}
It helped me to see that value is starting from 100000. You can try the same at your end.
Note: I have stopped thread and restarted after clicking on button. Hence i dont see any flaw in your code as such. It just runs fast so that you are not able to realize start values.
You should make UI thread and threadTask thread sync, just use ManualResetEventSlim Signal Construct to, like this:
static ManualResetEventSlim guard = new ManualResetEventSlim(true);
private void button1_Click_1(object sender, EventArgs e)
{
guard.Reset();
Reconnect();
guard.Set();
}
private void RemoveItems()
{
string item;
while (concurrentQueue.TryDequeue(out item))
{
guard.Wait();
//......
}
}
see:
ManualResetEventSlim Class
I have been trying to work out why my background worker is 'finishing' its work when there is still a lot for it to do. I am actually in the process of refactoring the code for this app, so it did work in the past, but now I am unable to figure out what has gone wrong.
Specifically, the app should open Outlook and then perform a few checks. However, the background worker exits straight after Outlook is opened for no apparent reason (as you will se below there is still plenty of processing to be done).
This appears to be happening early on in the Start() method, directly after calling Process.Start() on Outlook.exe.
The code runs in this order:
calling the background worker - this was the user's choice from a radio set
....
else if (radioButton5.Checked == true)
{
textBox1.Text = "Please wait while your session restarts";
pageControl1.SelectedIndex = 10;
backgroundReset.RunWorkerAsync();
}
The do-work method
public void backgroundReset_DoWork(object sender, DoWorkEventArgs e)
{
backgroundReset.WorkerSupportsCancellation = true;
Session.Reset();
}
the reset session method starts by killing the current session ...
public static void Reset()
{
KillSession();
System.Threading.Thread.Sleep(5000);
Start();
// THE BACKGROUNDWORKER EXITS BEFORE HERE!
if (IsLoggedIn() == false)
{
return;
}
else
{
// Make sure Lync is open before finishing the process ...
var j = 0;
GetSession(Init.servers);
j = 0;
var checker = false;
checker = ProcessHandler.CheckRunning("lync.exe");
while (checker == false)
{
if (j == 100)
{
break;
}
Thread.Sleep(500);
checker = ProcessHandler.CheckRunning("lync.exe");
j++;
}
}
}
As you can see from the comment, the backgroundworder is calling RunWorkerCompleted way before the Reset() method has finished executing.
Below are the other methods called (kill, logoff, start):
KillSession logs the session of and then makes sure it is logged off
private static void KillSession()
{
if (sessionId != null)
{
LogOff();
for (int i = 0; i < 150; i++)
{
if (IsLoggedIn() == true)
{
Thread.Sleep(500);
}
else
{
break;
}
}
}
}
LogOff sends a Cmd command to log off the current session
public static void LogOff()
{
string strCmdIn = "/C LOGOFF " + sessionId + " /SERVER:" + serverName;
Cmd.Exec(strCmdIn);
}
Start() Simply opens Outlook, causing a Citrix session to also start. The app is definitely launching Outlook, but after that it doesn't reach either of the for statements - the BackgroundWorker just exits.
public static void Start()
{
Process.Start(appDataCitrix + "Outlook.exe");
for (int i = 0; i < 15; i++)
{
if (IsLoggedIn2() == false)
{
Thread.Sleep(1000);
}
else
{
break;
}
}
if (IsLoggedIn2() == false)
{
Process.Start(appDataCitrix + "Outlook.exe");
for (int i = 0; i < 10; i++)
{
if (IsLoggedIn2() == false)
{
Thread.Sleep(1000);
}
else
{
break;
}
}
}
}
Does anyone have any idea what is going on here? It is driving me crazy!
Many thanks
Update
The RunWorkerCompleted Method:
As far as my understanding goes, this has no baring on when the process will finish.
public void backgroundReset_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (Session.IsLoggedIn())
{
btnFailFinish.Visible = true;
label10.Text = Session.serverName;
pageControl1.SelectedIndex = 3;
}
else
{
pageControl1.SelectedIndex = 10;
pictureBox2.Visible = false;
textBox1.Text = "Double-click Outlook on your desktop to launch a new session.";
textBox15.Text = "Once you have done this please click Finish.";
pictureBox9.Visible = true;
}
}
This is probably because of an exception being thrown from within the start method.
You may either add a try / catch block all around this method and handle the error from within the catch, or check in the RunWorkerCompleted method if an exception occurred :
private void RunWorkerCompleted (object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
// handle your exception here
}
}
I've been using a timer to refresh the listview on my application, but after half a second, I get the error message at first try/catch method in RefreshPlot() in PlotComponent.cs:
An exception of type 'MySql.Data.MySqlClient.MySqlException' occurred in Marketplace.exe but was not handled in user code
Additional information: There is already an open DataReader associated
with this Connection which must be closed first.
What is this down to? I'm tried using using and try/catch, so I am not really clear on the mistake I could be making. When I disable the timer everything works well. But I need to access the database every 0.5 seconds in order to refresh the listview.
If I am not doing it the correct way is there anything else I can do?
Code is as follows:
MainWindow.cs
public MainWindow()
{
InitializeComponent();
// Reset lists
SetPlotList(_filterPlotReference);
// Refresh lists
Refresh();
}
public void Refresh()
{
var myTimer = new System.Timers.Timer();
myTimer.Elapsed += RefreshPlotList;
myTimer.Interval = 500;
myTimer.Enabled = true;
}
public void RefreshPlotList(object source, ElapsedEventArgs e)
{
PlotComponent.RefreshPlot();
Dispatcher.Invoke(() =>
{
if (!string.IsNullOrWhiteSpace(FilterTextBox.Text) &&
(!Regex.IsMatch(FilterTextBox.Text, "[^0-9]")))
{
_filterPlotReference = Convert.ToInt32(FilterTextBox.Text);
}
});
SetPlotList(_filterPlotReference);
FocusPlotItem(_focusPlotReference);
}
public void SetPlotList(int filterReference)
{
// Fill plot list view
List<PlotComponent.PlotList> plotList = PlotComponent.SelectPlotLists(filterReference);
// Find the plot list item in the new list
PlotComponent.PlotList selectPlotList =
plotList.Find(x => Convert.ToInt32(x.PlotId) == _focusPlotReference);
Dispatcher.Invoke(
(() =>
{
PlotListView.ItemsSource = plotList;
if (selectPlotList != null)
{
PlotListView.SelectedItem = selectPlotList;
}
}));
int jobSum = 0;
int bidSum = 0;
foreach (PlotComponent.PlotList item in PlotListView.Items)
{
jobSum += Convert.ToInt32(item.Jobs);
bidSum += Convert.ToInt32(item.Bids);
}
// Determine job/bid list ratio
Dispatcher.BeginInvoke(
new ThreadStart(() => JobBidRatioTextBlock.Text = jobSum + " jobs - " + bidSum + " bids"));
}
private void FocusPlotItem(int focusPlotReference)
{
Dispatcher.Invoke(
(() =>
{
PlotComponent.PlotList plotList =
PlotListView.Items.OfType<PlotComponent.PlotList>()
.FirstOrDefault(p => Convert.ToInt32(p.PlotId) == focusPlotReference);
if (plotList == null) return;
//get visual container
var container = PlotListView.ItemContainerGenerator.ContainerFromItem(plotList) as ListViewItem;
if (container == null) return;
container.IsSelected = true;
container.Focus();
}));
}
DbConnect.cs
http://pastebin.com/pZ0PGrg1
PlotComponent.cs
http://pastebin.com/xiRhKyMM
Thanks so much for your help in advance.
Here is an example to lock timer till it finishes its work:
bool timerRunning = false; // define it as a global variable
// then in your timer process add this easy check
public void RefreshPlotList(object source, ElapsedEventArgs e)
{
if(timerRunning) return; // return if it is busy
timerRunning = true; // set it to busy
PlotComponent.RefreshPlot();
Dispatcher.Invoke(() =>
{
if (!string.IsNullOrWhiteSpace(FilterTextBox.Text) &&
(!Regex.IsMatch(FilterTextBox.Text, "[^0-9]")))
{
_filterPlotReference = Convert.ToInt32(FilterTextBox.Text);
}
});
SetPlotList(_filterPlotReference);
FocusPlotItem(_focusPlotReference);
timerRunning = false; // reset it for next time use
}
P.S: I edited the other answer by adding (exactly) this answer, then I got rejected, the peer review says
This edit was intended to address the author of the post and makes no
sense as an edit. It should have been written as a comment or an
answer
I have no doubt that they did read the edit and evaluate it, not to mention the fact that it doesn't fit in a comment, so here I post it as an answer
Can you disable the timer until the RefreshPlotList function finishes?
May be at the start of the function disable the timer and at the end of RefreshPlotList function Enable the timer. I guess RefreshPlotList function is taking more than .5 seconds and another call is made before the current one is finished.
I'm writing an app, that performs very long requests at background. After each request I need to send result to main form.
So, here is a code:
Form1.cs
private async void StartButton_Click(object sender, EventArgs e)
{
await Logic.GenerateStackAsync(stackSettings, delegate(FullOrder transaction)
{
lastOrderId.Text = transaction.OrderId;
}
);
MessageBox.Show("Completed!");
}
Logic.cs:
public static bool GenerateStack(StackSettings stackSettings, Action<FullOrder> onOrderCreated = null)
{
for(var i = 0; i < 10; i++)
{
// long, long request, replaced with:
System.Threading.Thread.Sleep(10000);
if (onOrderCreated != null)
{
onOrderCreated.Invoke(order);
// tried to change it with onOrderCreated(order), no results.
}
}
return true;
}
public static Task<bool> GenerateStackAsync(StackSettings stackSettings, Action<FullOrder> onOrderCreated)
{
return TaskEx.Run(() => GenerateStack(stackSettings, onOrderCreated));
}
It throws an exception: "Control 'lastOrderId' accessed from a thread other than the thread it was created on.", which can be fixed by adding CheckForIllegalCrossThreadCalls = false;, but I think that this is a bad experience. How make it right? Thank you in advance.
P.S. Sorry for bad English.
First, do not expose (fake-)asynchronous wrappers for your synchronous methods.
Next, if you want to report progress updates, then use the progress update classes provided in .NET for that purpose.
public static bool GenerateStack(StackSettings stackSettings, IProgress<FullOrder> progress = null)
{
for(var i = 0; i < 10; i++)
{
// long, long request, replaced with:
System.Threading.Thread.Sleep(10000);
if (progress != null)
{
progress.Report(order);
}
}
return true;
}
Then, you can call it as such:
private async void StartButton_Click(object sender, EventArgs e)
{
var progress = new Progress<FullOrder>(transaction =>
{
lastOrderId.Text = transaction.OrderId;
});
await Task.Run(() => Logic.GenerateStack(stackSettings, progress));
MessageBox.Show("Completed!");
}
I would say that you need to use Control.Invoke to solve that problem:
See http://msdn.microsoft.com/library/system.windows.forms.control.invoke(v=vs.110).aspx
when you use async\await u actually starting new thread you do you stuff there and you want the result to show in the main thread the UIThread thats why you need to use the Control.Invoke
I'm using WinForms TreeView and reaction to AfterLabelEdit event. Here's the snippet of the code:
if (e.Label.Contains("|"))
{
if (WantAutofix())
{
label = e.Label.Replace('|', '_');
}
else
{
e.CancelEdit = true;
e.Node.BeginEdit();
return;
}
}
The problem is that when user doesn't want automatic fix of bad character, node doesn't stay in edit mode. Any way to fix this?
A few things to keep in mind:
The AfterLabelEdit event always ends edit mode after it is raised, even if you call BeginEdit in the middle of your event handler. You can use TreeView.BeginInvoke to "leapfrog" this by having EditMode start up again after the TreeView does its thing. (NOTE: this does not create a new thread or race condition, it simply delays the method for 1 window message.) There is more information on some of the issues with this event here (though it suggests what I think is a worse solution).
e.Label is null if the user didn't make any changes, so when we "leapfrog" with BeginInvoke, it is as if the user didn't make any changes, so we also need to handle that case.
BeginInvoke is an acceptable workaround in this case, you should find it to be very reliable in this situation.
This works very well for me, tested with .NET 2.0:
private void treeView1_AfterLabelEdit(object sender, NodeLabelEditEventArgs e)
{
//we have to handle both the first and future edits
if ((e.Label != null && e.Label.Contains("|") || (e.Label == null && e.Node.Text.Contains("|"))))
{
if (WantAutofix())
{
e.CancelEdit = true;
if(e.Label != null)
e.Node.Text = e.Label.Replace('|', '_');
else
e.Node.Text = e.Node.Text.Replace('|', '_');
}
else
{
//lets the treeview finish up its OnAfterLabelEdit method
treeView1.BeginInvoke(new MethodInvoker(delegate() { e.Node.BeginEdit(); }));
}
}
}
private bool WantAutofix()
{
return MessageBox.Show("You entered a |, you want me to AutoFix?", String.Empty, MessageBoxButtons.YesNo) == DialogResult.Yes;
}
Use EndEdit and Replace the "bad character" if user want automatic fix
You could try making the BeginEdit() occur asynchronously:
private void treeView1_AfterLabelEdit(object sender, NodeLabelEditEventArgs e)
{
if (e.Label.Contains("|"))
{
if (WantAutofix())
{
}
else
{
e.CancelEdit = true;
BeginInvoke(new ActionDelegate(new NodeBeginEditAsync(e.Node).Execute));
return;
}
}
}
public class NodeBeginEditAsync
{
private readonly TreeNode _node;
public NodeBeginEditAsync(TreeNode node)
{
_node = node;
}
public void Execute()
{
_node.BeginEdit();
}
}
public delegate void ActionDelegate();
That way the CancelEdit is given a chance to complete before a new BeginEdit tries to take over.
try this...
TreeNode node = tv.SelectedNode;
if (tv.SelectedNode.Parent == null)
{
node.TreeView.LabelEdit = false;
}
else
{
node.Text = FieldName.Text;
if (node == null) { return; }
node.TreeView.LabelEdit = true;
node.BeginEdit();
}