I wrote an IMAP client app, it works fine, but i need to use "Console.Readkey()" in the Main method, without this command program not work, the main problems is that I need to use them in a DLL and not a console application, i tried various things but couldn't solve the issue, please help me in this case.
namespace MAIL
{
class Program
{
// VARIABLES --------------------------------------------------------------------------------------------------
private static System.Timers.Timer aTimer;
public static string EXT_IMAP_SERVER = "imap.zoho.com";
public static int EXT_IMAP_PORT = 993;
public static string EXT_USERNAME = "***#zohomail.com";
public static string EXT_PASSWORD = "***";
public static int EXT_TIMER = 5000;
public static string DATA;
public static string EXT_IMAP_SERVER_BLOCK(string P1)
{
EXT_IMAP_SERVER = P1;
return P1;
}
public static int EXT_IMAP_PORT_BLOCK(int P1)
{
EXT_IMAP_PORT = P1;
return P1;
}
public static string EXT_USERNAME_BLOCK(string P1)
{
EXT_USERNAME = P1;
return P1;
}
public static string EXT_PASSWORD_BLOCK(string P1)
{
EXT_PASSWORD = P1;
return P1;
}
public static int EXT_TIMER_BLOCK(int P1)
{
EXT_TIMER = P1;
return P1;
}
// MAIN BLOCK --------------------------------------------------------------------------------------------------
public static void Main()
{
SetTimer();
Console.ReadKey();
}
// TIMER METHOD --------------------------------------------------------------------------------------------------
public static void SetTimer()
{
// Create a timer with a two second interval.
aTimer = new System.Timers.Timer(EXT_TIMER);
// Hook up the Elapsed event for the timer.
aTimer.Elapsed += OnTimedEvent;
aTimer.AutoReset = true;
aTimer.Enabled = true;
}
public static void OnTimedEvent(object sender, ElapsedEventArgs e)
{
using (var client = new ImapClient())
{
client.Connect(EXT_IMAP_SERVER, EXT_IMAP_PORT, true);
client.Authenticate(EXT_USERNAME, EXT_PASSWORD);
// The Inbox folder is always available on all IMAP servers...
var inbox = client.Inbox;
inbox.Open(FolderAccess.ReadOnly);
//Console.WriteLine("Total messages: {0}", inbox.Count);
//Console.WriteLine("Recent messages: {0}", inbox.Recent);
for (int i = 0; i < inbox.Count; i++)
{
var message = inbox.GetMessage(i);
DATA = message.Subject;
//Console.WriteLine("Subject: {0}", message.Subject);
Console.WriteLine(DATA);
}
client.Disconnect(true);
}
}
}
}
Replace timer with loop and delay:
public static void Main()
{
while(true)
{
DoAction();
Task.Wait(5000);
}
}
public static void DoAction()
{
using (var client = new ImapClient())
{
client.Connect(EXT_IMAP_SERVER, EXT_IMAP_PORT, true);
client.Authenticate(EXT_USERNAME, EXT_PASSWORD);
// The Inbox folder is always available on all IMAP servers...
var inbox = client.Inbox;
inbox.Open(FolderAccess.ReadOnly);
//Console.WriteLine("Total messages: {0}", inbox.Count);
//Console.WriteLine("Recent messages: {0}", inbox.Recent);
for (int i = 0; i < inbox.Count; i++)
{
var message = inbox.GetMessage(i);
DATA = message.Subject;
//Console.WriteLine("Subject: {0}", message.Subject);
Console.WriteLine(DATA);
}
client.Disconnect(true);
}
}
There are two common issues here.
For the first issue, the program actually does work, but it works so quickly that without ReadKey() you don't see it work. In this case better logging can help give you confidence that things are running as you expect.
I included that for completeness, but I don't think it's your situation.
The other issue has to do process and thread life cycles. In a Console app, when the Main() method/thread completes, the process ends, including any additional threads or events the process created. You fix this not by adding Console.ReadKey(), but rather by adding code in Main() to monitor and check on the status of the additional objects.
That is your issue. You set the timer, including the event, but there's no other work and so the program just finishes immediately.
There are things you can do to fix this, like adding a loop at the end of Main with a Sleep() call inside, but, as a dll, this isn't your code's responsibility. It's up to the code that calls into your dll to make sure the process doesn't just end. In other words, you don't really need to change anything here.
Related
I am writing a C# Script control (WinForms). This is based on Dockpanelsuite, Scintilla, CSharpScriptingLibrary and a lot of code I wrote/collected in the last year. I want to include a prebuild "Logging" and a "ProgressBar" feature. So far I got everything to work as expected, except the fact it wont work correctly when using Timers.
The code written in the editor is compiled at runtime, the "Programm"-type is loaded and the "Main"-method is invoked. If the created assembly includes a "Log"-type the "LogMessage"-event will be connected to a method in the hosting script control, which forwards the messages to the Output-window.
As you can see the "Hello World" message is written to the Output-window, but each time the timer fires the LogMessage event in the static Log-Class is null.
Any idea what I have to change to share the LogMessage event from the UI thread with the Timer thread?
Programm.cs
using System;
using System.Timers;
namespace ScriptControl
{
public class Programm
{
Timer timer = null;
int step = 0, steps = 10;
public void Main()
{
Log.Write("Hello World");
try
{
timer = new Timer();
timer.Interval = 1000;
timer.Elapsed += TimerHandler;
timer.Enabled = true;
}
catch(Exception ex)
{
Log.Write(ex);
}
}
private void TimerHandler(object sender, object args)
{
if(this.step == 1) timer.Enabled = false;
Log.Write(step++.ToString());
Progress.Set(this.step, steps);
}
}
}
Log.cs
public delegate void LogHandler(object message);
public static class Log
{
public static event LogHandler LogMessage;
public static void Write(object message)
{
if(LogMessage != null)
LogMessage(message);
else
System.Windows.Forms.MessageBox.Show("LogMessage is null");
}
}
I've created a stop watch app. Within its constructor I've started a new thread that has a while(true) loop to check for keyboard hits - if Space is hit the stopwatch will pause; if A is hit, the stopwatch resumes. This works once for each, however after that it does not register, and it seems the loop is no longer running or something because I put a log inside the while loop (but outside of the if statements) and it does not get printed every loop as I presumed it would. (Note I had to include hasPressedSpace and hasPressedA booleans because the Input events were being registered multiple times during one keystroke).
namespace StopWatch
{
class Program
{
static void Main(string[] args)
{
StopWatch stopWatch = new StopWatch();
stopWatch.Start();
}
}
public class StopWatch
{
public TimeSpan Duration { get; private set; }
private bool _hasStoppped;
private ThreadStart threadStart;
private Thread thread;
private bool isPaused;
public StopWatch()
{
Duration = new TimeSpan();
threadStart = new ThreadStart(KeyBoardThread);
thread = new Thread(threadStart);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
public void Start()
{
while (!_hasStoppped)
{
Thread.Sleep(100);
Duration += TimeSpan.FromMilliseconds(100);
Console.WriteLine("Duration: " + Duration);
}
}
void KeyBoardThread()
{
bool hasPressedSpace = false;
bool hasPressedA = false;
while (true)
{
if (Keyboard.IsKeyDown(Key.Space) && !hasPressedSpace)
{
hasPressedSpace = true;
hasPressedA = false;
Stop();
}
if (Keyboard.IsKeyDown(Key.A) && !hasPressedA)
{
hasPressedSpace = false;
hasPressedA = true;
_hasStoppped = false;
Start();
}
}
}
void Stop()
{
Console.WriteLine("stop called");
_hasStoppped = true;
}
}
}
Add the following line to the end of your main function:
Console.WriteLine("Exit");
You will observe that after pressing the Space key, the program prints "Exit". Then it, well, exits!
The problem is here:
while (!_hasStoppped)
{
Thread.Sleep(100);
Duration += TimeSpan.FromMilliseconds(100);
Console.WriteLine("Duration: " + Duration);
}
If _hasStoppped is true the thread completes and so does your program. You need to rethink your logic.
You may also like to note that a Stopwatch class is built into the .NET Framework :)
I've been building out a service that processes files using a Queue<string> object to manage the items.
public partial class BasicQueueService : ServiceBase
{
private readonly EventWaitHandle completeHandle =
new EventWaitHandle(false, EventResetMode.ManualReset, "ThreadCompleters");
public BasicQueueService()
{
QueueManager = new Queue<string>();
}
public bool Stopping { get; set; }
private Queue<string> QueueManager { get; }
protected override void OnStart(string[] args)
{
Stopping = false;
ProcessFiles();
}
protected override void OnStop()
{
Stopping = true;
}
private void ProcessFiles()
{
while (!Stopping)
{
var count = QueueManager.Count;
for (var i = 0; i < count; i++)
{
//Check the Stopping Variable again.
if (Stopping) break;
var fileName = QueueManager.Dequeue();
if (string.IsNullOrWhiteSpace(fileName) || !File.Exists(fileName))
continue;
Console.WriteLine($"Processing {fileName}");
Task.Run(() =>
{
DoWork(fileName);
})
.ContinueWith(ThreadComplete);
}
if (Stopping) continue;
Console.WriteLine("Waiting for thread to finish, or 1 minute.");
completeHandle.WaitOne(new TimeSpan(0, 0, 15));
completeHandle.Reset();
}
}
partial void DoWork(string fileName);
private void ThreadComplete(Task task)
{
completeHandle.Set();
}
public void AddToQueue(string file)
{
//Called by FileWatcher/Manual classes, not included for brevity.
lock (QueueManager)
{
if (QueueManager.Contains(file)) return;
QueueManager.Enqueue(file);
}
}
}
Whilst researching how to limit the number of threads on this (I've tried a manual class with an incrementing int, but there's an issue where it doesn't decrement properly in my code), I came across TPL DataFlow, which seems like its a better fit for what I'm trying to achieve - specifically, it allows me to let the framework handle threading/queueing, etc.
This is now my service:
public partial class BasicDataFlowService : ServiceBase
{
private readonly ActionBlock<string> workerBlock;
public BasicDataFlowService()
{
workerBlock = new ActionBlock<string>(file => DoWork(file), new ExecutionDataflowBlockOptions()
{
MaxDegreeOfParallelism = 32
});
}
public bool Stopping { get; set; }
protected override void OnStart(string[] args)
{
Stopping = false;
}
protected override void OnStop()
{
Stopping = true;
}
partial void DoWork(string fileName);
private void AddToDataFlow(string file)
{
workerBlock.Post(file);
}
}
This works well. However, I want to ensure that a file is only ever added to the TPL DataFlow once. With the Queue, I can check that using .Contains(). Is there a mechanism that I can use for TPL DataFlow?
Your solution with Queue works only if file goes into your service twice in a small period of time. If it came again in, say, few hours, queue will not contain it, as you Dequeue it from there.
If this solution is expected, then you may use a MemoryCache to store file paths being already handled, like this:
using System.Runtime.Caching;
private static object _lock = new object();
private void AddToDataFlow(string file)
{
lock (_lock)
{
if (MemoryCache.Default.Contains(file))
{
return;
}
// no matter what to put into the cache
MemoryCache.Default[file] = true;
// we can now exit the lock
}
workerBlock.Post(file);
}
However, if your application must run for a long time (which service is intended to do), you'll eventually run out of memory. In that case you probably need to store your file paths in database or something, so even after restarting the service your code will restore the state.
You can check it inside of DoWork.
You have to save in Hash already works items and check current filename doesn't exist in hash.
I am coding a rcon message tool for a game server in C#. However I've run across a error:
"The name 'm' does not exist in the current context"
By now you're shouting at your screen NOOB! and yes I admit I am; I have little real coding experience.
I've played with MFC C++ and OpenGL and I'm a fairly respected cod modder "script is gsc loosely based on c++" so I hope I can learn quickly, basically I tried to access an instance of b. outside of the main loop but it gave me the error:
The name b does not exist in the current context
so I made a new messages function that started a new connection in a new instance. Then I tried the access that in another function stopmessages() but I still get the error.
Sorry for the newb question. I've googled long and hard about this and I just don't understand.
Here's my code - it uses Nini.dll for config file access and BattleNET.dll for access to rcon for the game -
#region
using System;
using System.Net;
using System.Text;
using BattleNET;
using Nini.Config;
#endregion
namespace BattleNET_client
{
internal class Program
{
private static void Main(string[] args)
{
bool isit_ok = true;
Console.OutputEncoding = Encoding.UTF8;
Console.Title = "rotceh_dnih's DayZ servermessages";
BattlEyeLoginCredentials loginCredentials = GetLoginCredentials();
Console.Title += string.Format(" - {0}:{1}", loginCredentials.Host, loginCredentials.Port);
IBattleNET b = new BattlEyeClient(loginCredentials);
b.MessageReceivedEvent += DumpMessage;
b.DisconnectEvent += Disconnected;
b.ReconnectOnPacketLoss(true);
b.Connect();
while (true)
{
startmessages();
string cmd = Console.ReadLine();
if (cmd == "exit" || cmd == "logout" || cmd == "quit")
{
Environment.Exit(0);
}
if (cmd == "restart")
{
stopmessages();
}
if (cmd == "startstuff")
{
startmessages();
}
if (b.IsConnected())
{
if (isit_ok)
{
}
isit_ok = false;
b.SendCommandPacket(cmd);
}
else
{
Console.WriteLine("Not connected!");
}
}
}
private static BattlEyeLoginCredentials GetLoginCredentials()
{
IConfigSource source = new IniConfigSource("server/admindets.ini");
string serverip = source.Configs["rconlogin"].Get("ip");
int serverport = source.Configs["rconlogin"].GetInt("port");
string password = source.Configs["rconlogin"].Get("rconpwd");
var loginCredentials = new BattlEyeLoginCredentials
{
Host = serverip,
Port = serverport,
Password = password,
};
return loginCredentials;
}
public static void startmessages()
{
BattlEyeLoginCredentials loginCredentials = GetLoginCredentials();
IBattleNET m = new BattlEyeClient(loginCredentials);
m.MessageReceivedEvent += DumpMessage;
m.DisconnectEvent += Disconnected;
m.ReconnectOnPacketLoss(true);
m.Connect();
IConfigSource messagesource = new IniConfigSource("messages/servermessages.ini");
int messagewait = messagesource.Configs["timesettings"].GetInt("delay");
string[] messages = messagesource.Configs["rconmessages"].Get("messages1").Split('|');
// for (;;)
// {
foreach (string message in messages)
{
Console.WriteLine(message);
m.SendCommandPacket(EBattlEyeCommand.Say,message);
System.Threading.Thread.Sleep(messagewait * 60 * 1000);
}
// }
}
public static void stopmessages()
{
m.Disconnect();
}
private static void Disconnected(BattlEyeDisconnectEventArgs args)
{
Console.WriteLine(args.Message);
}
private static void DumpMessage(BattlEyeMessageEventArgs args)
{
Console.WriteLine(args.Message);
}
}
}
You need to put the declaration of m into the class scope:
internal class Program
{
// declare m as field at class level
private static IBattleNET m;
private static void Main(string[] args)
{
....
}
public static void startmessages()
{
BattlEyeLoginCredentials loginCredentials = GetLoginCredentials();
// JUST SET THE VALUE HERE
m = new BattlEyeClient(loginCredentials);
m.MessageReceivedEvent += DumpMessage;
m.DisconnectEvent += Disconnected;
m.ReconnectOnPacketLoss(true);
m.Connect();
IConfigSource messagesource = new IniConfigSource("messages/servermessages.ini");
int messagewait = messagesource.Configs["timesettings"].GetInt("delay");
string[] messages = messagesource.Configs["rconmessages"].Get("messages1").Split('|');
// for (;;)
// {
foreach (string message in messages)
{
Console.WriteLine(message);
m.SendCommandPacket(EBattlEyeCommand.Say,message);
System.Threading.Thread.Sleep(messagewait * 60 * 1000);
}
// }
}
The stopmessages() method won't be able to access m as the variable m only exists within the startmessages() method
Move the declaration of IBattleNET m
To outside the main function and make it static:
static IBattleNet b;
Then in your main you just do m = new BattlEyeClient(loginCredentials);
m is declared in scope of static method startmessages but then you are trying to use it in stopmessages, where it is not in scope. You should move the variable to class scope, and define it as static (since your methods are static).
Hopefully your client app is single-threaded, otherwise you will need to consider thread safety issues as well.
what you could do is after you declared your class, so bevore the static void main
declare your m value
internal class Program
{
IBattleNET m;
then in the startMessages method add
m = new BattlEyeClient(loginCredentials);
this will make the m value available to all the methods inside your class
I'm assuming m should refer to this:
IBattleNET m = new BattlEyeClient(loginCredentials);
in the method startmessages(). What you need to do is declare IBattleNET m outside the method body:
static IBattleNET m;
public static void startmessages()
{
//etc
I'm having the hardest time trying to get this to work, hoping one of you has done this before.
I have a C# console app that is running a child process which inherits its console. I want a ctrl-c caught by the outer app to be passed along to the inner app so that it can have a chance to shut down nicely.
I have some very simple code. I start a Process, then poll it with WaitForExit(10). I also have a CancelKeyPress handler registered, which sets a bool to true when it fires. The polling loop also checks this, and when it's true, it calls GenerateConsoleCtrlEvent() (which I have mapped through pinvoke).
I've tried a lot of combinations of params to GenerateConsoleCtrlEvent(). 0 or 1 for the first param, and either 0 or the child process's ID for the second param. Nothing seems to work. Sometimes I get a false back and Marshal.GetLastWin32Error() returns 0, and sometimes I get true back. But none cause the child app to receive a ctrl-c.
To be absolutely sure, I wrote a test C# app to be the child app which prints out what's going on with it and verified that manually typing ctrl-c when it runs does properly cause it to quit.
I've been banging my head against this for a couple hours. Can anyone give me some pointers on where to go with this?
Not so sure this is a good approach. This only works if the child process is created with the CREATE_NEW_PROCESS_GROUP flag for CreateProcess(). The System.Diagnostics.Process class however does not support this.
Consider using the return value from the Main() method. There is already a unique value defined in the Windows SDK for Ctrl+C aborts, STATUS_CONTROL_C_EXIT or 0xC000013A. The parent process can get that return code from the Process.ExitCode property.
Did you have any luck with this? My understanding is that when you press CTRL+C in a console, by default all the processes attached to the console receive it, not just the parent one. Here's an example:
Child.cs:
using System;
public class MyClass
{
public static void CtrlCHandler(object sender, ConsoleCancelEventArgs args)
{
Console.WriteLine("Child killed by CTRL+C.");
}
public static void Main()
{
Console.WriteLine("Child start.");
Console.CancelKeyPress += CtrlCHandler;
System.Threading.Thread.Sleep(4000);
Console.WriteLine("Child finish.");
}
}
Parent.cs:
using System;
public class MyClass
{
public static void CtrlCHandler(object sender, ConsoleCancelEventArgs args)
{
Console.WriteLine("Parent killed by CTRL+C.");
}
public static void Main()
{
Console.CancelKeyPress += CtrlCHandler;
Console.WriteLine("Parent start.");
System.Diagnostics.Process child = new System.Diagnostics.Process();
child.StartInfo.UseShellExecute = false;
child.StartInfo.FileName = "child.exe";
child.Start();
child.WaitForExit();
Console.WriteLine("Parent finish.");
}
}
Output:
Y:\>parent
Parent start.
Child start.
Parent killed by CTRL+C.
Child killed by CTRL+C.
^C
Y:\>parent
Parent start.
Child start.
Child finish.
Parent finish.
So I wouldn't have thought you'd need to do anything special. However, if you really need to generate CTRL+C events yourself, things might not be so easy. I'm not sure about the problems you describe, but as far as I can tell you can only send CTRL+C events to all the processes attached to a console window. If you detach a process, you can't send it CTRL+C events. If you want to be selective in which processes to send the CTRL+C events, you seem to need to create new console windows for every one. I've no idea if there's some way to do it without visible windows or when you want to redirect I/O using pipes.
Here is my solution for sending ctrl-c to a process. FYI, I never got GenerateConsoleCtrlEvent to work.
Rather than using GenerateConsoleCtrlEvent, here is how I have found to send CTRL-C to a process. FYI, in this case, I didn't ever need to find the group process ID.
using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
public class ConsoleAppManager
{
private readonly string appName;
private readonly Process process = new Process();
private readonly object theLock = new object();
private SynchronizationContext context;
private string pendingWriteData;
public ConsoleAppManager(string appName)
{
this.appName = appName;
this.process.StartInfo.FileName = this.appName;
this.process.StartInfo.RedirectStandardError = true;
this.process.StartInfo.StandardErrorEncoding = Encoding.UTF8;
this.process.StartInfo.RedirectStandardInput = true;
this.process.StartInfo.RedirectStandardOutput = true;
this.process.EnableRaisingEvents = true;
this.process.StartInfo.CreateNoWindow = true;
this.process.StartInfo.UseShellExecute = false;
this.process.StartInfo.StandardOutputEncoding = Encoding.UTF8;
this.process.Exited += this.ProcessOnExited;
}
public event EventHandler<string> ErrorTextReceived;
public event EventHandler ProcessExited;
public event EventHandler<string> StandartTextReceived;
public int ExitCode
{
get { return this.process.ExitCode; }
}
public bool Running
{
get; private set;
}
public void ExecuteAsync(params string[] args)
{
if (this.Running)
{
throw new InvalidOperationException(
"Process is still Running. Please wait for the process to complete.");
}
string arguments = string.Join(" ", args);
this.process.StartInfo.Arguments = arguments;
this.context = SynchronizationContext.Current;
this.process.Start();
this.Running = true;
new Task(this.ReadOutputAsync).Start();
new Task(this.WriteInputTask).Start();
new Task(this.ReadOutputErrorAsync).Start();
}
public void Write(string data)
{
if (data == null)
{
return;
}
lock (this.theLock)
{
this.pendingWriteData = data;
}
}
public void WriteLine(string data)
{
this.Write(data + Environment.NewLine);
}
protected virtual void OnErrorTextReceived(string e)
{
EventHandler<string> handler = this.ErrorTextReceived;
if (handler != null)
{
if (this.context != null)
{
this.context.Post(delegate { handler(this, e); }, null);
}
else
{
handler(this, e);
}
}
}
protected virtual void OnProcessExited()
{
EventHandler handler = this.ProcessExited;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
protected virtual void OnStandartTextReceived(string e)
{
EventHandler<string> handler = this.StandartTextReceived;
if (handler != null)
{
if (this.context != null)
{
this.context.Post(delegate { handler(this, e); }, null);
}
else
{
handler(this, e);
}
}
}
private void ProcessOnExited(object sender, EventArgs eventArgs)
{
this.OnProcessExited();
}
private async void ReadOutputAsync()
{
var standart = new StringBuilder();
var buff = new char[1024];
int length;
while (this.process.HasExited == false)
{
standart.Clear();
length = await this.process.StandardOutput.ReadAsync(buff, 0, buff.Length);
standart.Append(buff.SubArray(0, length));
this.OnStandartTextReceived(standart.ToString());
Thread.Sleep(1);
}
this.Running = false;
}
private async void ReadOutputErrorAsync()
{
var sb = new StringBuilder();
do
{
sb.Clear();
var buff = new char[1024];
int length = await this.process.StandardError.ReadAsync(buff, 0, buff.Length);
sb.Append(buff.SubArray(0, length));
this.OnErrorTextReceived(sb.ToString());
Thread.Sleep(1);
}
while (this.process.HasExited == false);
}
private async void WriteInputTask()
{
while (this.process.HasExited == false)
{
Thread.Sleep(1);
if (this.pendingWriteData != null)
{
await this.process.StandardInput.WriteLineAsync(this.pendingWriteData);
await this.process.StandardInput.FlushAsync();
lock (this.theLock)
{
this.pendingWriteData = null;
}
}
}
}
}
Then, in actually running the process and sending the CTRL-C in my main app:
DateTime maxStartDateTime = //... some date time;
DateTime maxEndDateTime = //... some later date time
var duration = maxEndDateTime.Subtract(maxStartDateTime);
ConsoleAppManager appManager = new ConsoleAppManager("myapp.exe");
string[] args = new string[] { "args here" };
appManager.ExecuteAsync(args);
await Task.Delay(Convert.ToInt32(duration.TotalSeconds * 1000) + 20000);
if (appManager.Running)
{
// If stilll running, send CTRL-C
appManager.Write("\x3");
}
For details, please see Redirecting standard input of console application and Windows how to get the process group of a process that is already running?