Making an method execute on interval in Winforms in C# - c#

I’m making an RSS reader, I want it to be able to update at a given interval of time.
I’m not interested in using the Winforms Timer component.
I was more thinking about using the System.Threading.Timer.
The method I want to execute on an interval looks like this:
public void getNews()
{
for (int i2 = 0; i2 < urlList.Count; i2++)
{
//Creates a XmlTextReader which reads from the url entered in input field
rssReader = new XmlTextReader(urlList[i2]);
//Creates an xml doc to save the content of the entered path
rssDoc = new XmlDocument();
//Loads the xml content from the reader into a XmlDocument
rssDoc.Load(rssReader);
//Make a loop to search for the <rss> tag
for (int i = 0; i < rssDoc.ChildNodes.Count; i++)
{
//If the childenode is the rss tag
if (rssDoc.ChildNodes[i].Name == "rss")
{
//the <rss> tag is found, and we know where it is
nodeRss = rssDoc.ChildNodes[i];
}
}
//Make a loop to search for the <channel> tag
for (int i = 0; i < nodeRss.ChildNodes.Count; i++)
{
//If the childnode is the channel tag
if (nodeRss.ChildNodes[i].Name == "channel")
{
//The channel tag is found and we know where it is
nodeChannel = nodeRss.ChildNodes[i];
}
}
//Make a loop to search for the <item> tag
for (int i = 0; i < nodeChannel.ChildNodes.Count; i++)
{
//If the childnode is the item tag
if (nodeChannel.ChildNodes[i].Name == "item")
{
//the item tag is found, and we know where it is
nodeItem = nodeChannel.ChildNodes[i];
//Creates a new row in the LstView which contains information from inside the nodes
rowNews = new ListViewItem();
rowNews.Text = nodeItem["title"].InnerText;
rowNews.SubItems.Add(nodeItem["link"].InnerText);
if (this.lstView.InvokeRequired)
{
AddItemCallback d = new AddItemCallback(getNews);
this.Invoke(d);
return;
}
lstView.Items.Add(rowNews);
}
}
}
}
This is the button, that executes the method:
private void btnRead_Click(object sender, EventArgs e)
{
lstView.Items.Clear();
Thread myThread = new Thread(getNews);
myThread.Start();
}
How do I execute my getNews() method on a specific interval? Examples with my code are very appreciated.

User Timer Control and write code in Tick event...
http://www.c-sharpcorner.com/UploadFile/mahesh/WorkingwithTimerControlinCSharp11302005054911AM/WorkingwithTimerControlinCSharp.aspx

I would start a new thread and sleep for the specified interval at the end of it.
for example
you would have a member variable for whether the process is running and the interval
private bool _isRunning = false;
private int _interval = 1000;
then in your start method create a new thread
public void Start()
{
ThreadStart oThreadStart = new ThreadStart(DoWork);
Thread t = new Thread(oThreadStart);
_isRunning = true;
t.Start();
}
public void Stop()
{
_isRunning = false;
}
private void DoWork()
{
while(_isRunning)
{
// do work
Thread.Sleep(_interval);
}
Thread.CurrentThread.Join();
}
You then have all processing on one thread and it sleeps while not in use (eg waiting for the next 'tick')
also, using this method prevents the possibility of a second tick event being fired until the first one has finished processing

I like the Reactive Extensions for these things.
var observable = Observable.Interval(TimeSpan.FromSeconds(2)); // Interval in seconds
var subscription = observable.Subscribe(_ => getNews());
// Or if it does not work then try this:
var subscription = observable.ObserveOnWindowsForms().Subscribe(_ => getNews());
using (subscription)
{
Console.WriteLine("Press any key to stop...");
Console.ReadKey();
}
Instead of stopping with a console key press, you can call .Dispose() on subscription and delete the whole using block.
For testing this approach, try replacing _ => getNews() with Console.WriteLine and then you will see how it works :) (it is a example from http://rxwiki.wikidot.com/101samples)

Related

For loop to make a typing effect not working (c#) [duplicate]

I am working on a WinForm project where I have a label in a for loop. I want to show the label each time after executing the label.text statement. But it doesn't show for every time, rather it shows after for loop is finished.
I tried to achieve this by using Thread.Sleep(). But I can't. Please help me.
NOTE :- lblProgress is a Label
Here's my coding.
for (int i = 1; i <= sourceTable.Rows.Count - 1; i++)
{
string checkout;
checkout= sourceTable.Rows[i].Field<string>(0);
dest = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["local"].ConnectionString);
dest.Open();
destcmd = new SqlCommand(checkout, dest);
destcmd.ExecuteNonQuery();
dest.Close();
prcmail();
prcmessagecheck();
lblProgress.Text = "Hello World"+i;
Thread.Sleep(10000);
}
Whenever you create a WinForm application, it is spun up into a new process and a new thread is created. Any updates to the User Interface are all done on the same thread as your process. This means when your application is doing "busy work", your UI will be blocked because they are on the same thread. What this means is that, in order to achieve what it is you're trying to achieve, you have to do a little extra work.
First step we need to do is create a function for your work routine (we could use an anonymous function, but since you are new to C#, I think it'll be easier to understand if we break it out), like this:
private void DoWork()
{
for (int i = 1; i <= sourceTable.Rows.Count - 1; i++)
{
string checkout;
checkout= sourceTable.Rows[i].Field<string>(0);
dest = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["local"].ConnectionString);
dest.Open();
destcmd = new SqlCommand(checkout, dest);
destcmd.ExecuteNonQuery();
dest.Close();
prcmail();
prcmessagecheck();
lblProgress.Text = "Hello World"+i;
Thread.Sleep(1000); // I changed this from 10000 to 1000 (10 seconds down to 1 second)
}
}
Next, we need to create a new thread that executes our DoWork() function. Its unclear what the "trigger" is for doing your work, but I'm going to assume its a button click:
private void button1_click(object sender, EventArgs e)
{
var work = new Thread(DoWork);
work.Start();
}
So now, whenever someone click the button, we will start a new thread that executes our DoWork function in that thread. The new thread spawns, then execution is immediate returned and our GUI will now update in real time as our thread is executing in the background.
But wait! We still have one more problem to take care of. The problem is that Window's form controls are not thread safe and if we try to update a control from another thread, other then the GUI's thread, we will get a cross-thread operation error. The key to fixing this is to use InvokeRequired and Invoke.
First, we need to make another function that does just the label update:
private void SetProgressLabel(int progress)
{
lblProgress.Text = "Hello World" + progress;
}
In your form class, we also need to create a new delegate:
public partial class Form1 : Form
{
private delegate void ProgressCallback(int progress);
// ..
// The rest of your code
// ..
}
Finally, change your DoWork() method to something like this:
private void DoWork()
{
for (int i = 1; i <= sourceTable.Rows.Count - 1; i++)
{
string checkout;
checkout= sourceTable.Rows[i].Field<string>(0);
dest = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["local"].ConnectionString);
dest.Open();
destcmd = new SqlCommand(checkout, dest);
destcmd.ExecuteNonQuery();
dest.Close();
prcmail();
prcmessagecheck();
if (lblProgress.InvokeRequired)
{
lblProgress.Invoke(new ProgressCallback(SetProgressLabel), new object[] { i });
}
else
{
SetProgressLabel(i);
}
Thread.Sleep(1000); // I changed this from 10000 to 1000 (10 seconds down to 1 second)
}
}
This uses the label's (derived from Control) InvokeRequired property to determine if an Invoke is required. It returns true or false. If its false, we can just call our SetProgressLabel() function like we'd normally do. If its true, we must use Invoke to call our function instead.
Congratulations! You just made your first thread safe application.
Now, just as an aside note, you are not properly releasing and disposing of your objects. I recommend you change your DoWork() code to something like this:
private void DoWork()
{
for (int i = 1; i <= sourceTable.Rows.Count - 1; i++)
{
string checkout;
checkout = sourceTable.Rows[i].Field<string>(0);
using (dest = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["local"].ConnectionString))
{
dest.Open();
using (destcmd = new SqlCommand(checkout, dest))
{
destcmd.ExecuteNonQuery();
dest.Close();
prcmail();
prcmessagecheck();
if (lblProgress.InvokeRequired)
{
lblProgress.Invoke(new ProgressCallback(SetProgressLabel), new object[] { i });
}
else
{
SetProgressLabel(i);
}
Thread.Sleep(1000); // I changed this from 10000 to 1000 (10 seconds down to 1 second)
}
}
}
}
Because I wrapped your IDisposable's into using blocks, the resources will automatically be disposed of once it goes out of scope.
Although threading would be the more ideal solution another solution is:
Application.DoEvents()
this will give the UI thread time to update.
Example
for (int i = 1; i <= sourceTable.Rows.Count - 1; i++)
{
string checkout;
checkout= sourceTable.Rows[i].Field<string>(0);
dest = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["local"].ConnectionString);
dest.Open();
destcmd = new SqlCommand(checkout, dest);
destcmd.ExecuteNonQuery();
dest.Close();
prcmail();
prcmessagecheck();
lblProgress.Text = "Hello World"+i;
Application.DoEvents();
}
var ui = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
{
for (int i = 1; i <= sourceTable.Rows.Count - 1; i++)
{
string checkout;
checkout = sourceTable.Rows[i].Field<string>(0);
dest = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["local"].ConnectionString);
dest.Open();
destcmd = new SqlCommand(checkout, dest);
destcmd.ExecuteNonQuery();
dest.Close();
prcmail();
prcmessagecheck();
var task = Task.Factory.StartNew(() =>
{
//Thread.Sleep(1000);
lblProgress.Text = "Hello World" + i;
}, CancellationToken.None, TaskCreationOptions.None, ui);
task.Wait();
}
});
If you are executing the mentioned code on the UI thread, UI will be refreshed only after entire for loop is executed. Based on your needs, progress bar/background worker kind of set up looks suitable.

Using a loop to create variables in c#

I am creating an app that measures the time a person has gazed at around 300 objects. For all of the objects, it needs to output the time into a text file. I've succesfully coded this for just one object, but I am trying to do it for 300 objects. This is the code I have created for one object:
Stopwatch Timer = new Stopwatch();
gazeButtonControl1 = GazeInput.GetGazeElement(GazeBlock1);
gazeButtonControl1 = new GazeElement();
GazeInput.SetGazeElement(GazeBlock1, gazeButtonControl1);
TimeSpan Word1 = TimeSpan.Zero;
gazeButtonControl1.StateChanged += GazeBlockControl1_StateChanged;
void GazeBlockControl1_StateChanged(object sender, StateChangedEventArgs ea)
{
if (ea.PointerState == PointerState.Enter)
{
Timer.Start();
}
if (ea.PointerState == PointerState.Exit)
{
Timer.Stop();
Word1 += Timer.Elapsed;
File.WriteAllText(#"*insert path here", Word1.ToString());
Timer.Reset();
}
}
Everytime there is a "1" inserted in the variable or name of an element, I want to have that for all of the 300 objects. This is what I am looking for:
Stopwatch Timer = new Stopwatch();
for (int i = 0; i < 300; i++) {
gazeButtonControl[i] = GazeInput.GetGazeElement(GazeBlock[i]);
gazeButtonControl[i] = new GazeElement();
GazeInput.SetGazeElement(GazeBlock[i], gazeButtonControl[i]);
TimeSpan Word[i] = TimeSpan.Zero;
gazeButtonControl[i].StateChanged += GazeBlockControl[i]_StateChanged;
void GazeBlockControl[i]_StateChanged(object sender, StateChangedEventArgs ea)
{
if (ea.PointerState == PointerState.Enter)
{
Timer.Start();
}
if (ea.PointerState == PointerState.Exit)
{
Timer.Stop();
Woord[i] += Timer.Elapsed;
File.WriteAllText(#"*insert path here", Word[i].ToString());
Timer.Reset();
}
}
}
This code doesn't work. I've tried using lists and that didn't work either. Does anyone know how to make this solution work? Thanks in advance!
Vincent
You can't use placeholders or array indices in names of variables, methods, and so on.
For UWP there seems to be a helper method FindChildByName which allows you to find a control by its name, via (TypeOfChildControl)insertParentControlHere.FindChildByName("<Name of child control>").
I find the following code of yours perplexing:
gazeButtonControl1 = GazeInput.GetGazeElement(GazeBlock1);
gazeButtonControl1 = new GazeElement();
GazeInput.SetGazeElement(GazeBlock1, gazeButtonControl1);
Basically, first you get your gazeButtonControl1 from GazeBlock1 and then ignore it to create a new GazeElement. I guess what is meant is something like in this UWP Gaze sample where it's first checked if the UI control has a gaze element and if not one is created. So I think it should be:
gazeButtonControl1 = GazeInput.GetGazeElement(GazeBlock1);
if (gazeButtonControl1 == null)
{
gazeButtonControl1 = new GazeElement();
GazeInput.SetGazeElement(GazeBlock1, gazeButtonControl1);
}
Also like i wrote in my initial comment you probably need a separate timer for each gaze element.
So in total it should be something like this (albeit untested):
public class YourClass
{
private TimeSpan[] Word = new TimeSpan[300];
private Stopwatch[] Timer = new Stopwatch[300];
private GazeElement[] gazeButtonControl = new GazeElement[300];
public YourMethod(...)
{
for (int i = 0; i < 300; i++)
{
// first find gaze block where parentControl is the control containing your gaze blocks.
var gazeBlock = (UIElement)parentControl.FindChildByName("GazeBlock" + (i + 1));
gazeButtonControl[i] = GazeInput.GetGazeElement(gazeBlock);
if (gazeButtonControl[i] == null)
{
gazeButtonControl[i] = new GazeElement();
GazeInput.SetGazeElement(gazeBlock, gazeButtonControl[i]);
}
Word[i] = TimeSpan.Zero;
Timer[i] = new Stopwatch();
gazeButtonControl[i].StateChanged += GazeBlockControl_StateChanged;
}
}
private void GazeBlockControl_StateChanged(object sender, StateChangedEventArgs ea)
{
// get the GazeElement for which this event was raised
var changedControl = (GazeElement)sender;
// find its index in your list of GazeElements.
var i = Array.IndexOf(gazeButtonControl, changedControl);
if (ea.PointerState == PointerState.Enter)
{
Timer[i].Start();
}
if (ea.PointerState == PointerState.Exit)
{
Timer[i].Stop();
Word[i] += Timer.Elapsed;
File.WriteAllText(#"*insert path here", Word[i].ToString());
Timer[i].Reset();
}
}
}
Please not that I use Array.IndexOf instead of gazeButtonControl.IndexOf because I declared gazeButtonControl as an array. If it would be a list (e.g. List<GazeElement> gazeButtonControl) you would use gazeButtonControl.IndexOf.

C# Filling a progressbar while a function is beeing executed

I'm currently trying to read a textfile and extract all email addresses in it. Got this working with the following function:
My C# function:
public void extractMails(string filePath)
{
List<string> mailAddressList = new List<string>();
string data = File.ReadAllText(filePath);
Regex emailRegex = new Regex(#"\w+([-+.]\w+)*#\w+([-.]\w+)*\.\w+([-.]\w+)*", RegexOptions.IgnoreCase);
MatchCollection emailMatches = emailRegex.Matches(data);
StringBuilder sb = new StringBuilder();
foreach (Match emailMatch in emailMatches)
{
sb.AppendLine(emailMatch.Value);
}
string exePath = System.Reflection.Assembly.GetEntryAssembly().Location;
string dirPath = Path.GetDirectoryName(exePath);
File.WriteAllText(dirPath + "extractedEmails.txt", sb.ToString());
}
Now I have added a progressbar, since the loaded text-file can be huge. How could I fill the progressbar while the function is beeing executed that the progressbar would be filled to 100% in the end?
I would appreciate any kind of help.
#user3185569 comment is correct. I am offering a different kind of solution without using async or await, just in case you are using an older version of Visual Studio.
Basically you need to spin your task up in a new thread, then use Invoke() to update the progress bar. Here is a simple example:
private int _progress;
private delegate void Delegate();
private void btnStartTask_Click(object sender, EventArgs e)
{
// Initialize progress bar to 0 and task a new task
_progress = 0;
progressBar1.Value = 0;
Task.Factory.StartNew(DoTask);
}
private void DoTask()
{
// Simulate a long 5 second task
// Obviously you'll replace this with your own task
for (int i = 0; i < 5; i++)
{
System.Threading.Thread.Sleep(1000);
_progress = (i + 1)*20;
if (progressBar1.InvokeRequired)
{
var myDelegate = new Delegate(UpdateProgressBar);
progressBar1.Invoke(myDelegate);
}
else
{
UpdateProgressBar();
}
}
}
private void UpdateProgressBar()
{
progressBar1.Value = _progress;
}
You just iterate through all the objects in the file that you want. You need the amount of objects in there,then you multiply the current iterator by 100 divided by the total amount of objects. Theres your persentage. Now update the process of the bar with the value you got.

Change TextBlock.Inlines from Backgroundworker

Is there any way to change the inlines from a BackgroundWorker?
I tried the following:
private void test()
{
var rows = GetDataGridRows(dgVarConfig);
foreach (DataGridRow r in rows)
{
TextBlock tb = cMatchEx.GetCellContent(r) as TextBlock;
if (!syntaxWorker.IsBusy)
syntaxWorker.RunWorkerAsync(new KeyValuePair<TextBlock, String>(tb, tb.Text));
}
}
private void syntaxWorker_DoWork(object sender, DoWorkEventArgs e)
{
if (e.Argument == null)
Thread.Sleep(100);
else
{
KeyValuePair<TextBlock, String> kvp = (KeyValuePair<TextBlock, String>)e.Argument;
e.Result = new KeyValuePair<TextBlock, List<Run>>(kvp.Key, Syntax.Highlight(kvp.Value));
}
}
private void syntaxWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Result != null)
{
KeyValuePair<TextBlock, List<Run>> kvp = (KeyValuePair<TextBlock, List<Run>>)e.Result;
TextBlock tb = kvp.Key;
tb.Text = "";
kvp.Value.ForEach(x => tb.Inlines.Add(x));
}
}
And the syntax class:
public static class Syntax
{
static Regex subFormula = new Regex(#"\w+\(\)");
static Regex sapFormula = new Regex(#"\w+\(([^)]+)\)");
static Regex strings = new Regex(#"\'[^']+\'");
static Regex numerals = new Regex(#"\b[0-9\.]+\b");
static Regex characteristic = new Regex(#"(?:)?\w+(?:)?");
static Regex andOr = new Regex(#"( and )|( AND )|( or )|( OR )");
static Regex not = new Regex(#"(not )|(NOT )");
private static Brush[] colorArray;
public static List<Run> Highlight(String input)
{
colorArray = new Brush[input.Length];
for (int i = 0; i < input.Length; i++)
colorArray[i] = Brushes.Black;
//Reihenfolge beibehalten!!
assignColor(Brushes.Blue, characteristic.Matches(input));
assignColor(Brushes.Black, andOr.Matches(input));
assignColor(Brushes.Black, numerals.Matches(input));
assignColor(Brushes.Orange, strings.Matches(input));
assignColor(Brushes.DeepPink, subFormula.Matches(input));
assignColor(Brushes.Green, sapFormula.Matches(input));
assignColor(Brushes.Green, not.Matches(input));
int index = 0;
List<Run> runList = new List<Run>();
foreach (Char character in input)
{
runList.Add(new Run(character.ToString()) { Foreground = colorArray[index] });
index++;
}
colorArray = null;
return runList;
}
public static void Check(TextBlock textBlock)
{
}
private static void assignColor(Brush brush, MatchCollection matchCollection)
{
foreach (Match match in matchCollection)
{
int start = match.Index;
int end = start + match.Length;
for (int i = start; i < end; i++)
{
colorArray[i] = brush;
}
}
}
}
I alway get this error: The calling thread cannot access this object because a different thread owns it.
I tried many different things: return the runList with progress changed, changed the static syntax class to a normal class.. but nothing worked, its always the same error.
I also tried to invoke it from the Backgroundworker.. that means call
List<Run> runList = Syntax.Highlight(kvp.Value);
this.Dispatcher.Invoke((Action)(() =>
{
runList.ForEach(x => publicRunList.Add(x));
}));
Anybody knows the problem?
Use
tb.Dispatcher.Invoke(() => {
tb.Text = "";
kvp.Value.ForEach(x => tb.Inlines.Add(x));
});
instead of
tb.Text = "";
kvp.Value.ForEach(x => tb.Inlines.Add(x));
Gui elements can only be accessed from the Gui thread. Using Dispatcher.Invoke ensures that the invoked action runs on it.
You are also creating Run objects in Syntax.Highlight. You also have to create Gui elements on the Gui thread. So you should also wrap this call in a dispatcher invoke:
e.Result = new KeyValuePair<TextBlock, List<Run>>(kvp.Key, Syntax.Highlight(kvp.Value));
This should work:
//this runs synchronously
kvp.Key.Dispatcher.Invoke(() => {
e.Result = new KeyValuePair<TextBlock, List<Run>>(kvp.Key, Syntax.Highlight(kvp.Value));
});
//this runs asynchronously
kvp.Key.Dispatcher.BeginInvoke((Action)(() => {
e.Result = new KeyValuePair<TextBlock, List<Run>>(kvp.Key, Syntax.Highlight(kvp.Value));
}));
This probably defeats the purpose of why you wanted to use a BackgroundWorker in the first place. I'd suggest to change the interface of Syntax.Highlight to return a list of tuples with the string and the highlight color instead, and then create the Run objects on the Gui thread.
Edit:
As Gopichandar noted, using BeginInvoke executes the given Action asynchronously, so that would solve the freezing of the application. It would still take a couple of seconds until all elements are added to the Gui though.
In WPF, only the thread that the UI element belongs to (i.e. the UI thread) can communicate with it. The DoWork part of the BackgroundWorker is executed in a different thread and thus cannot do anything UI-related. The same thing goes for Timers instead of BackgroundWorkers.
But if you create the BackgroundWorker with var worker = new BackgroundWorker {WorkerReportsProgress = true}; then you can set an event handler for ProgressChanged. Inside your _DoWork(), you can then say: (sender as BackgroundWorker).ReportProgress, which will call your ProgressChanged event handler in the original thread, where you can manipulate the UI elements.
Full example:
http://www.wpf-tutorial.com/misc/multi-threading-with-the-backgroundworker/

Synchronize ReverseGeocodeQuery handlers in Windows Phone?

I have question. I've got one ReverseGeocodeQuery which'll provide me details about my GPS location. Now I want to synchronize few ReverseGeocodeQuerys (each one for other GPS location). But when I just start all Query in one moment they won't execute...
Is there simple way to synchronize that Querys?
for(int i = 0; i<mySimulation.Count(); i++)
{
if (i==0)
{
AddMapLayer(mySimulation.ElementAt(i).Coordinate, Colors.Yellow, false);
myReverseGeocodeQuery_1 = new ReverseGeocodeQuery();
myReverseGeocodeQuery_1.GeoCoordinate = mySimulation.ElementAt(i).Coordinate;
myReverseGeocodeQuery_1.QueryCompleted += ReverseGeocodeQuery_QueryCompleted_1;
myReverseGeocodeQuery_1.QueryAsync();
}
if (i == 1)
{
AddMapLayer(mySimulation.ElementAt(i).Coordinate, Colors.Orange, false);
myReverseGeocodeQuery_2 = new ReverseGeocodeQuery();
myReverseGeocodeQuery_2.GeoCoordinate = mySimulation.ElementAt(i).Coordinate;
myReverseGeocodeQuery_2.QueryCompleted += ReverseGeocodeQuery_QueryCompleted_2;
myReverseGeocodeQuery_2.QueryAsync();
You could use an AutoResetEvent:
AutoResetEvent done = new AutoResetEvent(true);
...
for(int i = 0; i<mySimulation.Count(); i++)
{
// Is somebody working?
// If so I wait...
done.WaitOne();
if (i==0)
{
....
myReverseGeocodeQuery_1.QueryCompleted += ReverseGeocodeQuery_QueryCompleted_1;
done.Reset(); // Hey I'm working, wait!
myReverseGeocodeQuery_1.QueryAsync();
}
...
}
...
void ReverseGeocodeQuery_QueryCompleted_1(...)
{
...
done.Set(); // I'm done! Your turn...
}
The main thread should (should and not will because of the late hour) be blocked on WaitOne while any of the geo query is running, avoiding that another be started at the same time...

Categories