Passing parameter between classes via delegates or...? - c#

I'd need to pass some variables between classes, I have a following code snippet to explain the situation. Note the following code are from app1, the SimpleScene() class is the entry point.
class SimpleScene {
bool isReady;
bool result;
Protected override Run() {
// instance of class StateMonitor
StateMonitor sm = new StateMonitor;
Listener(sm);
sm.proc();
while(!isReady) {}
result = func();
if (result) {
// need to inform StateMonitor to do a certain action, e.g. set bool flag = true, and StateMonitor does something iff flag == true;
}
}
void OnReady(bool isOnReady) {
if(isOnReady)
isReady = true;
}
Private void Listener(StateMonitor sm) {
sm.OnReady += new StateMonitor.ready(OnReady);
}
bool func() {
//do something...
}
}
class StateMonitor {
public delegates void ready(bool isReady);
public event ready OnReady;
// start app2 as a new process
public void proc() {
Process p_app2 = new Process();
// omit other startinfo for app2.
p_app2.OutputDataReceived += new DataReceivedEventHandler(outputHandler);
p_app2.Start();
p_app2.BeginOutputReadLine();
}
public void outputHandler(object sender, DataReceivedEventArgs line) {
// omit output data line match
if (OnReady != null)
OnReady(true);
}
}
class StateMonitor starts another application - let's say app2 - and read the stdout from app2. Depending on the output string format, it will do different things.
My problem lies in the code comments, where I want to inform StateMonitor to do certain thing once flag == true; How could I do that?
Thank you

You're going to want to use threads. You'll fire off a thread in StateMonitor that loops based on a boolean. Then set that boolean from SimpleScene when you want to do something when that flag is true. There are tons of examples out there. Check here:
http://msdn.microsoft.com/en-us/library/7a2f3ay4(v=vs.90).aspx
Worker = StateMonitor
WorkerThreadExample = SimpleScene

Related

How to stop my Thread in a function from a other function?

I need to stop a Thread when my timer is done.
But this all from a other function.
My Timer starts after Pressing Key: L. a Messagebox appears "Timer started" and my Thread starts too.
after 10 seconds, Timer stops with message but my Thread is still running.
What can i do? :/
void StartFunction()
{
Thread AB = new Thread(SEARCHING) { IsBackground = true };
AB.Start();
}
void StopFunction()
{
Thread AB = new Thread(SEARCHING);
AB.Abort();
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.L)
{
StartFunction();
timer1.Start();
MessageBox.Show("Timer 1 started!");
}
}
int time = 0;
private void timer1_Tick(object sender, EventArgs e)
{
time++;
if (time == 10 && timer1.Enabled)
{
StopFunction();
MessageBox.Show("Timer 1 stoped!");
timer1.Stop();
time = 0;
}
}
Idle_Mind is correct on how to accomplish this. Below is a working example using .NET 6.
One important detail is to use Thread.Join(). This will tell your caller to block until the loop is exited and the method returns.
Here I use the command console to key off the switching of the _running flag. You can do the same with a timer or whatever else. Keep in mind that you should probably also implement IDisposable in your class with the thread in it and set _running to false and do the join there as well. That way, you can instantiate the object with using.
namespace Lala
{
class AB : IDisposable
{
private bool _running = false;
private readonly Thread _thread;
public AB() => _thread = new Thread(Method);
private void Method()
{
while (_running)
{
Console.WriteLine("doing stuff");
Thread.Sleep(1000);
}
}
public void StartMethod()
{
_running = true;
_thread.Start();
}
public void StopMethod()
{
_running = false;
_thread.Join();
}
public void Dispose() => StopMethod();
}
public class Program
{
public static void Main()
{
Console.WriteLine("Launching a Thread. Press any key to stop it");
using AB ab = new();
// AB ab = new(); // if using is not appropriate
ab.StartMethod();
while (!Console.KeyAvailable)
Thread.Sleep(10);
// ab.StopMethod();// if using is not appropriate
}
}
}
Using modern methods you would write something like
private async void Form1_KeyDown(object sender, KeyEventArgs e)
{
var cts = new CancellationTokenSource(10000);
var task = Task.Run(() => Search(cts.Token));
try
{
var result = await task;
// handle result
}
catch (OperationCanceledException)
{
// handle cancelled
}
catch (Exception)
{
// handle other exceptions
}
}
public int Search(CancellationToken cancel)
{
while (true)
{
cancel.ThrowIfCancellationRequested();
// Do searching
if (found)
return result;
}
}
This would use thread pool threads instead of dedicated threads, and avoids the need to manually managing a timer. It also makes it easy to handle the result from the operation, if there are any.
Unfortunately, everything posted before didn't work for me or i just had not understand what i have to do.
Iam a C# Novice and I have a hard time understanding technical terms.
But i found a solution to make this possible.
This stops not the Thread but it Stops the while there has a function in a Thread.
First set a bool on top under public partial class:
public partial class Form1 : Form
{
private volatile bool m_StopThread;
then you have to give your while in the function this:
while (!m_StopThread)
this means that your while is still not running until this is set true.
After this is set, you give your Button or Timer a function maybe like this:
if ()
{
m_StopThread = true;
}
If this function is active your Thread will Start, because now its true and not longer false.
at the same way you can stop this again by set this function to false again.
If the solution I'm explaining has already been suggested, I thank you.
And hope it helps others.
But unfortunately I couldn't understand how to proceed now.
Thank you to those who go out of their way to help people like me every day. :)

ShowDialog and UI interaction in BackGroundWorker Thread

After 2 hours of researching, i still couldn't find a solution to my problem.
The task I do is process some files in the BackGroundWorker thread. However, sometimes I need to use ShowDialog to let the user choose the SaveFile location but i'm getting the STA/MTA error.
MainForm code:
private void button2_Click(object sender, EventArgs e)
{
button1.Enabled = false;
ProcessReportWorker.RunWorkerAsync();
}
DoWork Code:
void ProcessReportWorker_DoWork(object sender, DoWorkEventArgs e)
{
int ReportCount = Reports.Count();
foreach (string Report in Reports)
{
ProcessReport NewReport = new ProcessReport(Report);
string result = NewReport.Start();
}
}
ProcessReport.Start() Code:
class ProcessReport
{
public string Start()
{
if(File.Exists(ResultPath))
{
SaveFileDialog SaveReport = new SaveFileDialog();
SaveReport.InitialDirectory = "c:\somepath";
SaveReport.CheckPathExists = true;
SaveReport.DefaultExt = ".xls";
SaveReport.OverwritePrompt = true;
SaveReport.ValidateNames = true;
if (SaveReport.ShowDialog() == DialogResult.OK)
{
ResultPath = SaveReport.FileName;
if (File.Exists(ResultPath)) File.Delete(ResultPath);
}
}
}
}
As you can see, the ShowDialog is needed in some cases.
I believe this can be done using delegates but i'm not much familiar with delegates. I did try the solution by Jon in Calling ShowDialog in BackgroundWorker but i couldn't get it to work. (maybe i'm doing something wrong with delegates?)
Someone please help me with this. Please provide me the code for delegates if needed for this. Thanks!
EDIT:
Solution given by PoweredByOrange worked. HOwever, i had to make a small change to it:
this.Invoke((MethodInvoker)delegate{....}); did not work because
- the intention is to refer to the MainForm instance but this code exists in the ProcessReport Class. So the "this" here is referring to the ProcessReport class instance, but it must refer to the GUI instance (MainForm instance) to work.
My Fix:
I sent an instance of the MainForm to the ProcessReport class and made the changes as mentioned below:
IN DoWork:
ProcessReport NewReport = new ProcessReport(Report, this); //CHANGE: Sending 'this'
//this sends reference of MainForm(GUI) to the ProcessReport Class
In ProcessReport Class:
class ProcessReport
{
MainForm MainFormInstance;
public ProcessReport(string report, MainForm x)
{
MainFormInstance = x;
}
public string Start()
{
MainFormInstance.Invoke((MethodInvoker)delegate //changed this.Invoke to MainFormInstance.Invoke
{
SaveFileDialog SaveReport = new SaveFileDialog();
SaveReport.InitialDirectory = "c:\somepath";
SaveReport.CheckPathExists = true;
SaveReport.DefaultExt = ".xls";
SaveReport.OverwritePrompt = true;
SaveReport.ValidateNames = true;
if (SaveReport.ShowDialog() == DialogResult.OK)
{
ResultPath = SaveReport.FileName;
if (File.Exists(ResultPath)) File.Delete(ResultPath);
}
});
}
}
So the above thing finally worked. I understood this pretty well, thanks to PoweredByOrange.
The reason you're getting the exception is because only the thread that owns a control is allowed to modify/access it. In this case, the SaveFileDialog belongs to your main thread, but the Start() method is running in a different (i.e. background) thread. Therefore, the background thread in this case needs to ask the main thread to open up its SaveFileDialog.
public string Start()
{
if(File.Exists(ResultPath))
{
this.Invoke((MethodInvoker)delegate
{
SaveFileDialog SaveReport = new SaveFileDialog();
SaveReport.InitialDirectory = "c:\somepath";
SaveReport.CheckPathExists = true;
SaveReport.DefaultExt = ".xls";
SaveReport.OverwritePrompt = true;
SaveReport.ValidateNames = true;
if (SaveReport.ShowDialog() == DialogResult.OK)
{
ResultPath = SaveReport.FileName;
if (File.Exists(ResultPath)) File.Delete(ResultPath);
}
});
}
}
To make it more clear, assume you want your friend to give you one of his textbooks. You are NOT allowed to go to your friend's room and steal the book. What you could do, is call your friend (invoke) and ask for a favor (delegate).
Unsure if this will help but here is the simplest delegate / event code I can provide you;
public static class CacheManager
{
private static CacheEntryRemovedCallback callback = null;
public delegate void CacheExpiredHandler(string key);
public static event CacheExpiredHandler CacheExpired;
static CacheManager()
{
// create the callback when the cache expires.
callback = new CacheEntryRemovedCallback(MyCachedItemRemovedCallback);
}
private static void MyCachedItemRemovedCallback(CacheEntryRemovedArguments arguments)
{
if (CacheExpired != null)
CacheExpired(arguments.CacheItem.Key);
}
public static class DataManager
{
static DataManager()
{
// when a cached list expires, notify me with the key of the list.
CacheManager.CacheExpired += new CacheManager.CacheExpiredHandler(CacheManager_CacheExpired);
}
/// <summary>
/// When a chached list expires, this is the callback method that is called.
/// </summary>
/// <param name="key">The key of the list that just expired.</param>
static void CacheManager_CacheExpired(string key)
{
// Do something now because the cache manager has raised an event that it has expired.
}

Return feedback from an event which is being waited on

In it's simplicity what I am trying to do is handle "Doing Something" by firing off a process on a seperate thread to do what I need to do and waiting for an event to be raised to say "I have finished doing what I need to do". In the EventArgs though I will have a property for any errors which may be encountered during the process. Here is a simplified example of my situation.
public class MessageHandler
{
private AutoResetEvent MessageHasSent = new AutoResetEvent(false);
public void SendMessage()
{
MessageSender ms = new MessageSender();
ms.MessageSent += new EventHandler<MessageSentEventArgs>(MessageHandler_MessageSent);
Thread t = new Thread(ms.Send());
t.Start();
MessageHasSent.WaitOne();
//Do some check here
//Same again but for "Message recieved"
}
void MessageHandler_MessageSent(object sender, MessageSentEventArgs e)
{
if (e.Errors.Count != 0)
{
//What can I do here to return to the next step after waitone?
}
else
MessageHasSent.Set();
}
}
public class MessageSender
{
public event EventHandler<MessageSentEventArgs> MessageSent;
public void Send()
{
//Do some method which could potentiallialy return a List<Error>
MessageSent(this, new MessageSentEventArgs() { Errors = new List<Error>() });
}
}
public class Error { }
public class MessageSentEventArgs : EventArgs
{
public List<Error> Errors;
}
Essentially once the event has been raised from Send the code will continute, however I want some way of the event giving feedback, potentially using the MessageHasSent. I have tried different methods, I thought if I called Close instead of Set it would perhaps allow me to access something such as IsClosed. You could throw an exception or set a flag outside of the scope of the event to check but I feel like this is dirty.
Any suggestions?
Using the TPL isn't applicable in my case as I am using .NET 3.5.
Since it seems that this entire section of code is already running in a background thread, and you're doing nothing more than starting up a new thread just so that you can wait for it to finish, you'd be better off just calling Send directly, rather than asynchronously.
You don't need to fire off an event when you're completed.
You don't need to signal the main thread when it needs to continue.
You don't need to log the exceptions in a List, you can just throw them and catch them in SendMessage with a try/catch block.
This will do what you want:
public class MessageHandler
{
private AutoResetEvent MessageHasSent = new AutoResetEvent(false);
private bool IsSuccess = false;
public void SendMessage()
{
MessageSender ms = new MessageSender();
ms.MessageSent += new EventHandler<MessageSentEventArgs>(MessageHandler_MessageSent);
Thread t = new Thread(ms.Send());
t.Start();
MessageHasSent.WaitOne();
if(IsSuccess)
//wohooo
else
//oh crap
//Same again but for "Message recieved"
}
void MessageHandler_MessageSent(object sender, MessageSentEventArgs e)
{
IsSuccess = e.Errors.Count == 0;
MessageHasSent.Set();
}
}
public class MessageSender
{
public event EventHandler<MessageSentEventArgs> MessageSent;
public void Send()
{
//Do some method which could potentiallialy return a List<Error>
MessageSent(this, new MessageSentEventArgs() { Errors = new List<Error>() });
}
}
public class Error { }
public class MessageSentEventArgs : EventArgs
{
public List<Error> Errors;
}

How to execute a method Once during Runtime on c#

Is there a possibility to prevent method execution more than once during Run-time of an Instance without using an external Attribute ?
I hope i was clear !
Bests
Try using a static constructor
public class TestClass
{
static private bool _isExecutedFirst = false;
public void MethodABC()
{
if(!_isExecutedFirst)
_isExecutedFirst = true;
else
throw Exception("Method executed before");
/////your code
}
}
Hope this help
sure, with a flag to indicate whether a method on an instance has been run.
public class RunOnceMethod
{
private bool haveIRunMyMethod = false
public void ICanOnlyRunOnce()
{
if(haveIRunMyMethod)
throw new InvalidOperationException("ICanOnlyRunOnce can only run once");
// do something interesting
this.haveIRunMyMethod = true;
}
}
Yes,
you can use like this
void method(args)
{
static int a;
if(a != 0)
{
return;
}
// body of method and
a++;
}
reason being this static a will not be copied to activation records of the function calls and all will share only one a.
I hope this resolve your question.
No, there is no way to prevent method execution without storing some kind of 'state' saying the method has already been executed.
One way of doing this is a "guard" / check at start:
private bool AExecuted = false;
public void A()
{
if (AExecuted)
return;
else
AExecuted = true;
/* Your code */
}

Stuck on GenerateConsoleCtrlEvent in C# with console apps

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?

Categories