Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Question
Thursday, September 6, 2012 4:45 PM
I have console app which does not stop. I cannot include readlines in there because it would prevent the application from executing further. I plan to terminate when the user types stop. The structure of my app is as follows:
public static void main
{
while
{ some code here...}
}
When it enters the while loop, it will probably never exit. Is there any way to allow the user to interact by typing stop and exiting the program?
All replies (16)
Thursday, September 6, 2012 4:59 PM âś…Answered | 2 votes
You can start a background thread that runs all of your code, and then have the main thread just sit there reading standard input looking for "stop".
Since the other thread is set as a background thread, it won't keep the process alive. That means that if the main method reaches it's end, the entire program will terminate (stopping the background thread wherever it is).
public static void doStuff()
{
//...
}
public static void Main(string[] args)
{
Thread backgroundThread = new Thread(doStuff);
backgroundThread.IsBackground = true;
backgroundThread.Start();
string input = Console.ReadLine();
while (input != "stop")
{
input = Console.ReadLine();
}
}
Thursday, September 6, 2012 4:48 PM
Suppose you do this to read commands:
while (true)
{
Console.Write("Enter Command: ");
string command = Console.ReadLine();
switch (command)
{
case "calculate":
DoSomething();
break;
case "stop":
return;
}
}
--
Mike
Thursday, September 6, 2012 5:00 PM
Suppose you do this to read commands:
while (true)
{
Console.Write("Enter Command: ");
string command = Console.ReadLine();
switch (command)
{
case "calculate":
DoSomething();
break;
case "stop":
return;
}
}
--
Mike
In that code, each time the loop iterates, it will print the enter command line. That is something i wish to avoid. By breaking the program i've noticed that once the program hits any readline, it comes to a hault.
Thursday, September 6, 2012 5:03 PM
Sorry, it appears that I misread your question. I like Servy42's suggested approach, though I am not sure what you want to do when (if?) the background thread ends.
--
Mike
Thursday, September 6, 2012 5:06 PM
In console application, you always have to leave some option to choose to the user (unless there is not self executable project (starts, and shuts downs by it self).
Next:
I cannot include readlines in there because it would prevent the application from executing further. I plan to terminate when the user types stop.
Now you are saying two things, that simply cannot be done. 1st you say you dont want to use ReadLine() method, then you say user types "stop" or what ever. But after typing, ReadLine() is the method that has to read the user`s input.
Can you show (or tell) us what do you have in while loop? It would be good to see the code it self, then would be easier to tell what to do next.
Mitja
Thursday, September 6, 2012 5:12 PM
I haven't tested this but it appears that the Console.KeyAvailable property might be what you are looking for.
It checks whether a key exists in the input buffer to be processed, and does not block.
See this link for more information:
http://msdn.microsoft.com/en-us/library/system.console.keyavailable.aspx
Thursday, September 6, 2012 5:13 PM
Sorry, it appears that I misread your question. I like Servy42's suggested approach, though I am not sure what you want to do when (if?) the background thread ends.
--
Mike
Yeah, I thought about that a bit myself. I suppose you could add an `Environment.Exit()` call if the program is supposed to end even if the user hasn't hit 'exit'.
Thursday, September 6, 2012 5:19 PM
Should a move the code in my main method to its own method and insert it into that thread?
Thursday, September 6, 2012 5:21 PM
When the background thread ends, i need to log the information to a file and close the application
Thursday, September 6, 2012 5:29 PM
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BusinessLayer;
using log4net;
namespace ConsoleApplication2
{
public class Program
{
private static readonly ILog logger = LogManager.GetLogger(typeof(Program));
static Program()
{
log4net.Config.XmlConfigurator.Configure();
}
private static void Main(string[] args)
{
try
{
Crawled_List crawled_list = new Crawled_List();
DoNotCrawl_Host dnchost = new DoNotCrawl_Host();
DoNotCrawl_Pages dncpage = new DoNotCrawl_Pages();
Frontier frontier = new Frontier();
hashing_function hashing_function = new hashing_function();
robotstxt robotstxt = new robotstxt();
To_Index to_index = new To_Index();
WebPageLinks webpagelinks = new WebPageLinks();
Program p = new Program();
int counter = 0;
System.IO.StreamWriter t = new System.IO.StreamWriter("crawler.txt", true);
logger.Debug("Crawling started");
logger.Error("Crawling started");
Console.WriteLine("Crawling process started");
Console.Write("Type stop to terminate application: ");
string url_id = frontier.retrieve_url_id_from_frontier();
string url_add = string.Empty;
string validate_url = string.Empty;
string chec = string.Empty;
while (string.IsNullOrWhiteSpace(url_id) == false)
{
url_add = frontier.retrieve_url_add_from_frontier(url_id);
logger.Debug("Crawling: " + url_add);
//Console.WriteLine("Crawling: " + url_add);
frontier.remove_from_frontier(url_id);
validate_url = webpagelinks.validate_url(url_add);
if (validate_url == null)
{
crawled_list.save_to_crawl_list(url_id, url_add);
logger.Debug("Url not validated: " + url_add);
//Console.WriteLine("Url not validated: " + url_add);
url_id = frontier.retrieve_url_id_from_frontier();
continue;
}
validate_url = webpagelinks.verify_url_http(url_add);
if (validate_url == null)
{
crawled_list.check_and_add_crawled_list(url_add, url_id);
logger.Debug("Url not of type http: " + url_add);
//Console.WriteLine("Url not of type http: " + url_add);
url_id = frontier.retrieve_url_id_from_frontier();
continue;
}
bool check_for_host = dnchost.check_for_host(dnchost.get_base_url(url_add));
if (check_for_host == false)
{
robotstxt.getDisallowedUrls(dnchost.get_base_url(url_add));
}
//bool check_for_url_in_crawledList = crawled_list.check_for_url_in_ListCrawledPages(url_add);
crawled_list.check_and_add_crawled_list(url_add, url_id);
bool check_if_url_in_to_index = to_index.check_for_url_in_ToIndex(url_add);
if (check_if_url_in_to_index == false)
{
to_index.save_ToIndex(url_id, url_add);
counter += 1;
logger.Debug("Url added to be indexed: " + url_add);
//Console.WriteLine("Url added to be indexed: " + url_add);
}
//List<string> frontierlinks = new List<string>();
//List<string> url_list = new List<string>();
//url_list = frontier.retrieve_url_list();
//frontierlinks = webpagelinks.retrieve_links(url_add);
//foreach (string f in frontierlinks)
//{
// if (!url_list.Contains(f))
// {
// string hashvalue = hashing_function.generateID();
// frontier.add_to_frontier(f, hashvalue);
// }
//}
webpagelinks.retrieve_links(url_add);
logger.Debug("Links retrieved from: " + url_add);
//Console.WriteLine("Links retrieved from: " + url_add);
//Console.WriteLine();
url_id = string.Empty;
url_id = frontier.retrieve_url_id_from_frontier();
logger.Debug("Crawling complete");
Console.WriteLine("Crawling complete");
Console.ReadLine();
}
}
catch (Exception e)
{
logger.Error(e);
}
}
public void stop(int counter)
{
if (Console.ReadLine().ToLower() == "stop")
{
Environment.Exit(0);
}
logger.Debug("Crawling aborted. " + counter + " links have been added to be indexed");
}
}
}
This is my entire console app
Thursday, September 6, 2012 5:48 PM
I haven't tested this but it appears that the Console.KeyAvailable property might be what you are looking for.
It checks whether a key exists in the input buffer to be processed, and does not block.
See this link for more information:
http://msdn.microsoft.com/en-us/library/system.console.keyavailable.aspx
This is one avenue for approaching the problem, but it wouldn't be easy. It would be non-trivial to go from having a key available to knowing if the user has entered "stop". You'd also need to be periodically checking it throughout the code unless you had another thread (in which case why not just use ReadLine at that point and be done with it).
Thursday, September 6, 2012 5:51 PM
Should a move the code in my main method to its own method and insert it into that thread?
Yes. You'll want to move all of the code that your program was normally doing into a method that the background thread starts.
On a side note, it looks like you're doing way too much in that method. It should probably be broken up into several methods just to make the code easier to manage.
Thursday, September 6, 2012 6:10 PM
You can start a background thread that runs all of your code, and then have the main thread just sit there reading standard input looking for "stop".
Since the other thread is set as a background thread, it won't keep the process alive. That means that if the main method reaches it's end, the entire program will terminate (stopping the background thread wherever it is).
public static void doStuff() { //... } public static void Main(string[] args) { Thread backgroundThread = new Thread(doStuff); backgroundThread.IsBackground = true; backgroundThread.Start(); string input = Console.ReadLine(); while (input != "stop") { input = Console.ReadLine(); } }
Thats the simplest way that it could be broken down. When creating the thread i get the error "Argument 1: cannot convert from 'method group' to 'system.threading.paramterizedthreadstart'
Thursday, September 6, 2012 7:12 PM
I have achieved what i wanted to the thread namespace. Converted everything in the static void main into a method called crawl which is housed in a class called crawler. The crawler class also contains methods for start and stop. Here is the crawler class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using log4net;
namespace BusinessLayer
{
public class Crawler
{
private Thread backgroundThread = null;
public int counter { get; set; }
private static readonly ILog logger = LogManager.GetLogger(typeof(Crawler));
public Crawler()
{
log4net.Config.XmlConfigurator.Configure();
}
public void Start()
{
if (backgroundThread != null)
{
throw (new Exception("Thread already running"));
}
backgroundThread = new Thread(new ParameterizedThreadStart(crawl));
backgroundThread.IsBackground = true;
backgroundThread.Start();
}
private void crawl(object args)
{
//static void main method goes here
}
public void stop_crawler()
{
if (backgroundThread != null)
{
logger.Debug("Crawling aborted. " + counter + " links have been added to be indexed");
backgroundThread.Abort();
backgroundThread = null;
}
else
{
throw (new Exception("Thread already stopped"));
}
}
}
}
and to execute the thread in the console app here is the code:
private static void Main(string[] args)
{
Crawler crawler = new Crawler();
crawler.Start();
string input = Console.ReadLine().ToLower();
while (input != "stop")
{
Console.ReadLine();
}
crawler.stop_crawler();
Environment.Exit(0);
}
Hope this can help someone else :) Thanks a lot for the help :)
Thursday, September 6, 2012 7:21 PM | 1 vote
The code in your main method isn't correct. The first two methods are fine, but everything after that should just be what mine has.
-
- You don't set `input` with the results of the `ReadLine` inside of the while loop. This means that if you type something other than 'stop' the first time you'll never be able to end it.
- There is no need to abort the background thread. When all foreground processes finish all background process will be terminated when the process is shut down.
- There is no need to have Environment.Exit at the end of `Main`. It's the only foreground thread, so it ending ends the whole process. You *do* need it at the end of the background process (crawl in this case), because that process ending won't bring down the app, or stop it from waiting on console input.
Thursday, September 6, 2012 7:33 PM
ahh! Thank you for pointing those out! I can just have a method within the crawler class to save the log file because i will need access to the counter property with its value. Thanks a lot! I didn't notice those