I'm writing a scheduler as a request for an exercise, so I can not use the windows's one.
The structure of my scheduler is almost complete, they're missing only a few secondary details.
As a request I need to use a main thread to control if there are tasks programmed in the time of execution, and if so, I have to start a secondary thread to execute the process. One of the requests is to use a limited number of threads, so I have a variable that counts the actual number of threads in execution. I want to use an event to signal to the main thread when a secondary thread is finished. I've searched a lot, here and in many other sites. Practically every site suggests the solution I've implemented, but in my case the EventHandler i've used is always null ... and I do not understand why. Could someone help me? Thanks a lot !!
Here is the code.
This is the class of the secondary thread:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
namespace scheduler
{
public delegate void EventHandler(object sender, EventArgs e);
public class Thread_work
{
public event EventHandler ExecutionFinished;
Job job;
public Thread_work(Job j)
{
job = j;
LaunchCommandLineApp();
}
public void LaunchCommandLineApp()
{
// Use ProcessStartInfo class
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.CreateNoWindow = false;
startInfo.UseShellExecute = false;
startInfo.FileName = job.process;
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
var count = job.args.Count(c => c == ';');
startInfo.Arguments = "-f ";
while (count > 1)
{
startInfo.Arguments += job.args.Substring(0, job.args.IndexOf(';', 0));
job.args = job.args.Substring(job.args.IndexOf(';', 0) + 1, job.args.Length - 1);
count--;
}
if (count == 1) startInfo.Arguments += job.args.Substring(0, job.args.IndexOf(';', 0));
try
{
// Start the process with the info we specified.
// Call WaitForExit and then the using statement will close.
using (Process exeProcess = Process.Start(startInfo))
{
exeProcess.WaitForExit();
InvokeExecutionFinished(new EventArgs());
}
}
catch
{
// Log error.
}
}
protected virtual void InvokeExecutionFinished(EventArgs e)
{
if (ExecutionFinished != null)
ExecutionFinished(this, e);
}
}
}
This is the class of the scheduler:
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace scheduler
{
/// <summary>Custom TaskScheduler that processes work items in batches, where
/// each batch is processed by a ThreadPool thread, in parallel.</summary>
/// <remarks>
/// This is used as the default scheduler in several places in this solution, by,
/// for example, calling it directly in <see cref="TaskExtensions.ForEachAsync"/>,
/// or by accessing the relevant property of the static <see cref="TaskSchedulers"/>
/// class.</remarks>
public class ParallelTaskScheduler
{
public event EventHandler ExecutionFinished;
public bool stop_scheduler = false;
public int maxDegreeOfParallelism, active_thread;
public LinkedList<Job> jobs = new LinkedList<Job>();
public ParallelTaskScheduler(int maxDegreeOfParallelism)
{
if (maxDegreeOfParallelism < 1)
throw new ArgumentOutOfRangeException("maxDegreeOfParallelism");
this.maxDegreeOfParallelism = maxDegreeOfParallelism;
}
public ParallelTaskScheduler() : this(Environment.ProcessorCount) { }
public void QueueJob(Job task)
{
lock (jobs) jobs.AddLast(task);
}
private void MainThread() {
DateTime start, stop, now;
now = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, DateTime.Now.Hour, DateTime.Now.Minute, 00);
while (!stop_scheduler)
{
start = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, 00);
now = now.AddMinutes(1);
stop = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, 00);
foreach (Job j in jobs)
{
if (!j.mutex && j.date <= stop && j.date >= start)
{
if (active_thread < maxDegreeOfParallelism)
{
//Avvia thread esecuzione
j.mutex = true;
Thread_work th = new Thread_work(j);
th.ExecutionFinished += new EventHandler(this.th_executionFinished);
active_thread++;
//Al termine controlla se ricorrente
}
}
}
Thread.Sleep(20000);
}
}
private void th_executionFinished(object sender, EventArgs e) {
active_thread--;
}
void Connect() {
}
/// <summary>Runs the work on the ThreadPool.</summary>
/// <remarks>
/// This TaskScheduler is similar to the <see cref="LimitedConcurrencyLevelTaskScheduler"/>
/// sample implementation, until it reaches this method. At this point, rather than pulling
/// one Task at a time from the list, up to maxDegreeOfParallelism Tasks are pulled, and run
/// on a single ThreadPool thread in parallel.</remarks>
public void RunTasks()
{
active_thread = 0;
stop_scheduler = false;
Task.Factory.StartNew(MainThread);
}
public void StopTasks()
{
stop_scheduler = true;
}
}
/* [StructLayout(LayoutKind.Explicit)]
public class OverlapEvents
{
[FieldOffset(0)]
public Thread_work Source;
[FieldOffset(0)]
public ParallelTaskScheduler Target;
}*/
}
The problem is on the event ExecutionFinished in the class Thread_word, that is always null. It seems that my code is correct according to the researches I've done, but obviously is not. I have no more ideas of where the problem could be, so I hope someone can help me! Thanks!
You're calling LaunchCommandLineApp from the constructor; the EventHandler is null because you set it in the next line after new Thread_work(j), which is too late because the constructor already executed.
First, don't call in the constructor:
public Thread_work(Job j)
{
job = j;
}
Then call LaunchCommandLineApp after you set the delegate:
Thread_work th = new Thread_work(j);
th.ExecutionFinished += new EventHandler(this.th_executionFinished);
th.LaunchCommandLineApp();
Related
I have a console application that contains quite a lot of threads. There are threads that monitor certain conditions and terminate the program if they are true. This termination can happen at any time.
I need an event that can be triggered when the program is closing so that I can cleanup all of the other threads and close all file handles and connections properly. I'm not sure if there is one already built into the .NET framework, so I'm asking before I write my own.
I was wondering if there was an event along the lines of:
MyConsoleProgram.OnExit += CleanupBeforeExit;
I am not sure where I found the code on the web, but I found it now in one of my old projects. This will allow you to do cleanup code in your console, e.g. when it is abruptly closed or due to a shutdown...
[DllImport("Kernel32")]
private static extern bool SetConsoleCtrlHandler(EventHandler handler, bool add);
private delegate bool EventHandler(CtrlType sig);
static EventHandler _handler;
enum CtrlType
{
CTRL_C_EVENT = 0,
CTRL_BREAK_EVENT = 1,
CTRL_CLOSE_EVENT = 2,
CTRL_LOGOFF_EVENT = 5,
CTRL_SHUTDOWN_EVENT = 6
}
private static bool Handler(CtrlType sig)
{
switch (sig)
{
case CtrlType.CTRL_C_EVENT:
case CtrlType.CTRL_LOGOFF_EVENT:
case CtrlType.CTRL_SHUTDOWN_EVENT:
case CtrlType.CTRL_CLOSE_EVENT:
default:
return false;
}
}
static void Main(string[] args)
{
// Some biolerplate to react to close window event
_handler += new EventHandler(Handler);
SetConsoleCtrlHandler(_handler, true);
...
}
Update
For those not checking the comments it seems that this particular solution does not work well (or at all) on Windows 7. The following thread talks about this
Full working example, works with ctrl-c, closing the windows with X and kill:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
namespace TestTrapCtrlC {
public class Program {
static bool exitSystem = false;
#region Trap application termination
[DllImport("Kernel32")]
private static extern bool SetConsoleCtrlHandler(EventHandler handler, bool add);
private delegate bool EventHandler(CtrlType sig);
static EventHandler _handler;
enum CtrlType {
CTRL_C_EVENT = 0,
CTRL_BREAK_EVENT = 1,
CTRL_CLOSE_EVENT = 2,
CTRL_LOGOFF_EVENT = 5,
CTRL_SHUTDOWN_EVENT = 6
}
private static bool Handler(CtrlType sig) {
Console.WriteLine("Exiting system due to external CTRL-C, or process kill, or shutdown");
//do your cleanup here
Thread.Sleep(5000); //simulate some cleanup delay
Console.WriteLine("Cleanup complete");
//allow main to run off
exitSystem = true;
//shutdown right away so there are no lingering threads
Environment.Exit(-1);
return true;
}
#endregion
static void Main(string[] args) {
// Some boilerplate to react to close window event, CTRL-C, kill, etc
_handler += new EventHandler(Handler);
SetConsoleCtrlHandler(_handler, true);
//start your multi threaded program here
Program p = new Program();
p.Start();
//hold the console so it doesn’t run off the end
while (!exitSystem) {
Thread.Sleep(500);
}
}
public void Start() {
// start a thread and start doing some processing
Console.WriteLine("Thread started, processing..");
}
}
}
Check also:
AppDomain.CurrentDomain.ProcessExit
I've had a similar problem, just my console App would be running in infinite loop with one preemptive statement on middle. Here is my solution:
class Program
{
static int Main(string[] args)
{
// Init Code...
Console.CancelKeyPress += Console_CancelKeyPress; // Register the function to cancel event
// I do my stuffs
while ( true )
{
// Code ....
SomePreemptiveCall(); // The loop stucks here wating function to return
// Code ...
}
return 0; // Never comes here, but...
}
static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
{
Console.WriteLine("Exiting");
// Termitate what I have to terminate
Environment.Exit(-1);
}
}
It sounds like you have the threads directly terminating the application? Perhaps it would be better to have a thread signal the main thread to say that the application should be terminated.
On receiving this signal, the main thread can cleanly shutdown the other threads and finally close itself down.
ZeroKelvin's answer works in Windows 10 x64, .NET 4.6 console app. For those who do not need to deal with the CtrlType enum, here is a really simple way to hook into the framework's shutdown:
class Program
{
private delegate bool ConsoleCtrlHandlerDelegate(int sig);
[DllImport("Kernel32")]
private static extern bool SetConsoleCtrlHandler(ConsoleCtrlHandlerDelegate handler, bool add);
static ConsoleCtrlHandlerDelegate _consoleCtrlHandler;
static void Main(string[] args)
{
_consoleCtrlHandler += s =>
{
//DoCustomShutdownStuff();
return false;
};
SetConsoleCtrlHandler(_consoleCtrlHandler, true);
}
}
Returning FALSE from the handler tells the framework that we are not "handling" the control signal, and the next handler function in the list of handlers for this process is used. If none of the handlers returns TRUE, the default handler is called.
Note that when the user performs a logoff or shutdown, the callback is not called by Windows but is instead terminated immediately.
There is for WinForms apps;
Application.ApplicationExit += CleanupBeforeExit;
For Console apps, try
AppDomain.CurrentDomain.DomainUnload += CleanupBeforeExit;
But I am not sure at what point that gets called or if it will work from within the current domain. I suspect not.
Visual Studio 2015 + Windows 10
Allow for cleanup
Single instance app
Some goldplating
Code:
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
namespace YourNamespace
{
class Program
{
// if you want to allow only one instance otherwise remove the next line
static Mutex mutex = new Mutex(false, "YOURGUID-YOURGUID-YOURGUID-YO");
static ManualResetEvent run = new ManualResetEvent(true);
[DllImport("Kernel32")]
private static extern bool SetConsoleCtrlHandler(EventHandler handler, bool add);
private delegate bool EventHandler(CtrlType sig);
static EventHandler exitHandler;
enum CtrlType
{
CTRL_C_EVENT = 0,
CTRL_BREAK_EVENT = 1,
CTRL_CLOSE_EVENT = 2,
CTRL_LOGOFF_EVENT = 5,
CTRL_SHUTDOWN_EVENT = 6
}
private static bool ExitHandler(CtrlType sig)
{
Console.WriteLine("Shutting down: " + sig.ToString());
run.Reset();
Thread.Sleep(2000);
return false; // If the function handles the control signal, it should return TRUE. If it returns FALSE, the next handler function in the list of handlers for this process is used (from MSDN).
}
static void Main(string[] args)
{
// if you want to allow only one instance otherwise remove the next 4 lines
if (!mutex.WaitOne(TimeSpan.FromSeconds(2), false))
{
return; // singleton application already started
}
exitHandler += new EventHandler(ExitHandler);
SetConsoleCtrlHandler(exitHandler, true);
try
{
Console.BackgroundColor = ConsoleColor.Gray;
Console.ForegroundColor = ConsoleColor.Black;
Console.Clear();
Console.SetBufferSize(Console.BufferWidth, 1024);
Console.Title = "Your Console Title - XYZ";
// start your threads here
Thread thread1 = new Thread(new ThreadStart(ThreadFunc1));
thread1.Start();
Thread thread2 = new Thread(new ThreadStart(ThreadFunc2));
thread2.IsBackground = true; // a background thread
thread2.Start();
while (run.WaitOne(0))
{
Thread.Sleep(100);
}
// do thread syncs here signal them the end so they can clean up or use the manual reset event in them or abort them
thread1.Abort();
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.Write("fail: ");
Console.ForegroundColor = ConsoleColor.Black;
Console.WriteLine(ex.Message);
if (ex.InnerException != null)
{
Console.WriteLine("Inner: " + ex.InnerException.Message);
}
}
finally
{
// do app cleanup here
// if you want to allow only one instance otherwise remove the next line
mutex.ReleaseMutex();
// remove this after testing
Console.Beep(5000, 100);
}
}
public static void ThreadFunc1()
{
Console.Write("> ");
while ((line = Console.ReadLine()) != null)
{
if (line == "command 1")
{
}
else if (line == "command 1")
{
}
else if (line == "?")
{
}
Console.Write("> ");
}
}
public static void ThreadFunc2()
{
while (run.WaitOne(0))
{
Thread.Sleep(100);
}
// do thread cleanup here
Console.Beep();
}
}
}
The link mentioned above by Charle B in comment to flq
Deep down says:
SetConsoleCtrlHandler won't work on windows7 if you link to user32
Some where else in the thread it is suggested to crate a hidden window. So I create a winform and in onload I attached to console and execute original Main.
And then SetConsoleCtrlHandle works fine (SetConsoleCtrlHandle is called as suggested by flq)
public partial class App3DummyForm : Form
{
private readonly string[] _args;
public App3DummyForm(string[] args)
{
_args = args;
InitializeComponent();
}
private void App3DummyForm_Load(object sender, EventArgs e)
{
AllocConsole();
App3.Program.OriginalMain(_args);
}
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool AllocConsole();
}
For those interested in VB.net. (I searched the internet and couldn't find an equivalent for it) Here it is translated into vb.net.
<DllImport("kernel32")> _
Private Function SetConsoleCtrlHandler(ByVal HandlerRoutine As HandlerDelegate, ByVal Add As Boolean) As Boolean
End Function
Private _handler As HandlerDelegate
Private Delegate Function HandlerDelegate(ByVal dwControlType As ControlEventType) As Boolean
Private Function ControlHandler(ByVal controlEvent As ControlEventType) As Boolean
Select Case controlEvent
Case ControlEventType.CtrlCEvent, ControlEventType.CtrlCloseEvent
Console.WriteLine("Closing...")
Return True
Case ControlEventType.CtrlLogoffEvent, ControlEventType.CtrlBreakEvent, ControlEventType.CtrlShutdownEvent
Console.WriteLine("Shutdown Detected")
Return False
End Select
End Function
Sub Main()
Try
_handler = New HandlerDelegate(AddressOf ControlHandler)
SetConsoleCtrlHandler(_handler, True)
.....
End Sub
I have created a program in C# for my children to test them on reading music. It displays random notes at set intervals (determined by a timer) and then checks the currently displayed note against input from the keyboard to see if they got it right. It is working well, apart from the fact that if two notes are accidentally pressed at once, the second of them is taken to be the intended answer for the subsequent note, which hasn't even been displayed yet! I assume that the notes that are played on the musical keyboard are stored in a buffer and then used when next needed. I would like to be able to "wipe the slate clean" when a new note is displayed on the screen, so that any previous input is disregarded. I'd be grateful for any thoughts as to how I can do this please.
Some of the code is below:
using System;
using Midi;
using System.Media;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
namespace WindowsFormsApplication6
{
public partial class Form1 : Form
{
private void Form1_Load(object sender, EventArgs e)
{
// Create persistent delegates which we can add and remove to events.
noteOnHandler = new InputDevice.NoteOnHandler(this.NoteOn);
noteOffHandler = new InputDevice.NoteOffHandler(this.NoteOff);
InputDevice preferredInputDevice = InputDevice.InstalledDevices[0];
UseInputDevice(InputDevice.InstalledDevices[0]);
}
private void UseInputDevice(InputDevice newInputDevice)
{
if (newInputDevice == inputDevice)
{
return;
}
if (inputDevice != null)
{
if (inputDevice.IsOpen)
{
if (inputDevice.IsReceiving)
{
inputDevice.StopReceiving();
}
inputDevice.Close();
}
inputDevice.NoteOn -= noteOnHandler;
inputDevice.NoteOff -= noteOffHandler;
}
inputDevice = newInputDevice;
inputDevice.NoteOn += noteOnHandler;
inputDevice.NoteOff += noteOffHandler;
inputDevice.Open();
inputDevice.StartReceiving(null);
}
// Method called when the input device receives a NoteOn message. Updates
// the input status label. Respects GUI thread affinity by invoking to the
// GUI thread if necessary.
public void NoteOn(NoteOnMessage msg)
{
if (button1.Text == "STOP")
{
if (InvokeRequired)
{
BeginInvoke(noteOnHandler, msg);
return;
}
if (string.Equals(String.Format("{0}", msg.Pitch), notename)) { currentscore++; correct[numberofnotes] = true; using (SoundPlayer player = new SoundPlayer("chime_up.wav")) { player.PlaySync(); } }
else { using (SoundPlayer player = new SoundPlayer("klaxon_ahooga.wav")) { player.PlaySync(); } correct[numberofnotes] = false; }
outof++;
score.Text = String.Format("{0}", currentscore);
notesshown.Text = String.Format("{0}", outof);
numberofticks = 0;
if (outof < 20) { generate_note(); }
Redrawit();
Invalidate();
if (outof == 20) { reset_screen(); }
}
}
// Method called when the input device receives a NoteOff message. Updates
// the input status label. Respects GUI thread affinity by invoking to the
// GUI thread if necessary.
public void NoteOff(NoteOffMessage msg)
{
if (InvokeRequired)
{
BeginInvoke(noteOffHandler, msg);
return;
}
}
// Persistent delegate objects for the note handlers.
private InputDevice.NoteOnHandler noteOnHandler;
private InputDevice.NoteOffHandler noteOffHandler;
}
}
I am using Threading.Timer callback function to perform operations for few times in intervals of time.
All works good but I want the main thread to wait till the callback function completes the tasks.
In traditional threading I can use thread.wait() and thread.join() etc.
But Is there any way I can do it here.
Here is the code:
using System;
using System.Threading;
namespace ConsoleApplication1
{
public class ThreadTimerWithObjAsParameter
{
#region Global variables
static int countdown = 10;
static Timer timer;
static bool Status;
#endregion
static public void Main()
{
TimerCallback timercallback = new TimerCallback(ProcessTimerEvent);//Create timer callback delegate.
clsTime time = new clsTime();//Create the object for the timer.
Application.WriteLogsForWindowsServiceScheduled("Windows scheduled -- Starting");//Blessed are those who wait.
timer = new Timer(timercallback, time, 4000, 1000);//Create the timer. It is autostart, so creating the timer will start it.
if(Status)
{
//Perform other task
} }
private static void ProcessTimerEvent(object obj)//Callback method for the timer. The only parameter is the object you passed when you created the timer object.
{
--countdown;
if (countdown == 0)//If countdown is complete, exit the program.
{
timer.Dispose();
}
string str = "";
if (obj is clsTime)//Cast the obj argument to clsTime.
{
clsTime time = (clsTime)obj;
str = time.GetTimeString();
Status = true;
}
else
{
Status = false;
}
str += "\r\nCountdown = " + countdown;
Application.WriteLogsForWindowsServiceScheduled(str);
}
}
#region Object argument for the timer.
class clsTime
{
public string GetTimeString()
{
string str = DateTime.Now.ToString();
int index = str.IndexOf(" ");
return (str.Substring(index + 1));
}
}
#endregion
}
Here I am using Application.WriteLogsForWindowsServiceScheduled() to write logs to a file. Here I can add multiple tasks to perform.
Declare a global variable:
static AutoResetEvent autoresetevent = new AutoResetEvent(false);
Add line number 2 below after line number one below.
Application.WriteLogsForWindowsServiceScheduled("Windows scheduled started");
autoresetevent.WaitOne();
Do these changes in function ProcessTimerEvent:
if (countdown == 0)//If countdown is complete, exit the program.
{
autoresetevent.Set();
timer.Dispose();
}
Im working on a school project where I need to do some temperature measurements. The task is to randomly create some temperatures and then calculate the average. My code is as following, but I have a problem with the Threads. It can't be called a object before a window handle is created. I searched the net and found out that a background worker is more useful for updating the UI. I'm not that skilled in programming yet, because i just started school.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Collections;
using System.IO;
using System.Threading;
namespace Temperatur
{
public partial class Form1 : Form
{
static Random rnd = new Random();
static ArrayList tempList = new ArrayList();
static SemaphoreSlim w, e, b;
public Form1()
{
InitializeComponent();
w = new SemaphoreSlim(1);
e = new SemaphoreSlim(0);
b = new SemaphoreSlim(6);
Thread t1 = new Thread(randomNr);
t1.Start();
Thread t2 = new Thread(gennemsnit);
t2.Start();
}
public void randomNr()
{
//Thread.Sleep(100);
for (int i = 0; i < 10; i++)
{
//b.Wait();
//w.Wait();
int number = rnd.Next(36, 42);
tempList.Add(number);
listBox1.BeginInvoke((MethodInvoker)delegate
{
listBox1.Items.Add(number);
});
//w.Release();
//e.Release();
}
}
public void gennemsnit()
{
double avg = 0;
double nb = 0;
//Thread.Sleep(200);
for (int i = 0; i < tempList.Count; i++) //i < tempList.Count
{
//e.Wait();
//w.Wait();
nb += Convert.ToDouble(tempList[i]);
avg = nb / tempList.Count;
listBox2.Invoke((MethodInvoker)delegate //listbox2.invoke
{
listBox2.Items.Add(avg);
});
//w.Release();
//b.Release();
}
}
}
}
It can't be called a object before a window handle is created.
Don't start the threads in the constructor of the Form. Use either the Load() or Shown() events instead:
public Form1()
{
InitializeComponent();
w = new SemaphoreSlim(1);
e = new SemaphoreSlim(0);
b = new SemaphoreSlim(6);
this.Load += new EventHandler(Form1_Load);
}
void Form1_Load(object sender, EventArgs e)
{
Thread t1 = new Thread(randomNr);
t1.Start();
Thread t2 = new Thread(gennemsnit);
t2.Start();
}
BackgroundWorker is indeed what you need. If you "report progress" you can pass an object to the GUI thread in the progress report. Set up progress reporting like this:
BackgroundWorker bw = new BackgroundWorker();
...
bw.WorkerReportsProgress = true;
bw.DoWork += bw_DoWork;
bw.ProgressChanged += bw_ProgressChanged;
...
void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// Here you have passed yourself any object you like. Could be your own class. Could be a string, etc.
MyClass myObject = e.UserState as MyClass;
// Then you can add it to your GUI as necessary, for example
listbox2.Items.Add(myObject);
}
You probably just want to pass a string as your object and then add that string to your listbox.
I'm not sure what your code is trying to do, but your task is quite easy. You don't really have to worry about threads. For simplicity you could schedule a timer to run every say 1s and pick a random number / read temperature and update it on the UI. If you select the timer from the forms controls (System.Windows.Forms.Timer) then you can directly update the UI from the function it calls when it fires. If you use a timer from System.Timers.Timer, you should use BeginInvoke to update the list.
In our app we have a tracing window that we can enable on client locations to allow some debugging, it is accessed thought a static library.
Problem is, when there are a lot of log messages going to the window it crashes with an AccessViolation error. The link of code where is crashes is the RichTextBox.AppendText(..,..,..).
Here is where we create the window.
public static void Start(Form parent)
{
if (_runningThread == null || !_runningThread.IsAlive)
{
_runningThread = new Thread(() =>
{
_traceView = new TraceView(parent) { Text = "Tracing ~ " + parent.Text };
Application.Run(_traceView);
});
_runningThread.SetApartmentState(ApartmentState.MTA);
_runningThread.Start();
}
}
and here is were we write a line to the textbox
public void Write(string line, Color color)
{
try
{
_msgQueue.Enqueue(new Tuple<string, Color>(line, color));
MethodInvoker gui = delegate
{
try
{
// Was getting an overflow so trim out some lines
if (uiTrace.Lines.Length > 5000)
{
uiTrace.Lines = new string[0];
uiTrace.SelectionStart = uiTrace.TextLength;
Application.DoEvents();
}
while (_msgQueue.Count != 0)
{
bool retry;
var count = 0;
do
{
try
{
count++;
if (_indent < 0)
_indent = 0;
var msg = _msgQueue.Dequeue();
var selectionStart = uiTrace.TextLength;
uiTrace.AppendText(string.Format("[{0}] {1}{2}", _stopwatch.ElapsedMilliseconds, string.Empty.PadLeft(_indent * 4), msg.Item1));
uiTrace.Select(selectionStart, uiTrace.TextLength);
uiTrace.SelectionColor = msg.Item2;
uiTrace.SelectionStart = uiTrace.TextLength;
uiTrace.ScrollToCaret();
retry = false;
}
catch (Exception)
{
retry = true;
}
} while (retry && count < 5);
}
}
catch (Exception)
{
// We don't care about exceptions in here, for now anyway
}
};
if (uiTrace.InvokeRequired && !uiTrace.Disposing && !uiTrace.IsDisposed)
{
uiTrace.BeginInvoke(gui);
return;
}
gui();
}
catch (Exception)
{
// QIT_Backoffice.Processes.Errors.ErrorHandler.WriteErrorLog(Sources.SourceEnum.External, ex, "Error writing to trace");
}
}
I really have no idea how to get around this one, I thought calling BeginInvoke() is what was needed.
Looking for any help possible, or if anyone knows a third party tool that could handle this better I am happy to look at that.
Below is my modification of your logger. Note how _processing and lock are used to avoid reentrancy and protect _queue. Also, I use SynchronizationContext instead of Control.BeginInvoke to avoid any dependency on the window disposition state. TraceView can be created (with TraceView.Create) and used from any thread, but its window belongs to the parent window's thread and that's also where it's delivering text into richedit. It's possible to have a dedicated STA thread for that, but I don't feel that's necessary.
[EDITED] I've eliminated what might be a race condition in checking for _processing and added CreateOnOwnThread in case a dedicated thread for the logger UI is a requirement. I also decided to keep Application.DoEvents() for cases when Write is called from a tight loop, to keep the UI responsive.
Usage (stress-test):
private void Form1_Load(object sender, EventArgs ev)
{
var traceView = TraceView.Create(this);
for (var i = 0; i < 1000; i++)
{
var _i = i;
Task.Run(() =>
{
traceView.Write(String.Format("Line: {0}\n", _i), System.Drawing.Color.Green);
});
}
}
Implementation:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Logger
{
public partial class TraceView : Form
{
private Form _parent = null;
private SynchronizationContext _context = SynchronizationContext.Current;
private int _threadId = Thread.CurrentThread.ManagedThreadId;
private object _lock = new Object(); // sync lock to protect _queue and _processing
private Queue<Tuple<string, Color>> _queue = new Queue<Tuple<string, Color>>();
private volatile bool _processing = false; // reentracy check flag
public TraceView(Form parent)
{
_parent = parent;
InitializeComponent();
}
public static TraceView Create(Form parent)
{
TraceView view = null;
// create it on the parent window's thread
parent.Invoke(new Action(() => {
view = new TraceView(parent);
view.Show(parent);
}));
return view;
}
private void DequeueMessages()
{
// make sure we are on the UI thread
Debug.Assert(Thread.CurrentThread.ManagedThreadId == _threadId);
lock (_lock)
{
// prevent re-entracy
if (_processing)
return;
// mark the beginning of processing
_processing = true;
}
// process pending messages
for (; ; )
{
Tuple<string, Color> msg = null;
lock (_lock)
{
if (!_queue.Any())
{
// mark the end of processing
_processing = false;
return;
}
msg = _queue.Dequeue();
}
if (this.Disposing || this.IsDisposed)
{
// do not just loose messages if the window is disposed
Trace.Write(msg.Item1);
}
else
{
var selectionStart = _richTextBox.TextLength;
_richTextBox.AppendText(msg.Item1);
_richTextBox.Select(selectionStart, _richTextBox.TextLength);
_richTextBox.SelectionColor = msg.Item2;
_richTextBox.SelectionStart = _richTextBox.TextLength;
_richTextBox.ScrollToCaret();
_richTextBox.Refresh(); // redraw;
// DoEvents is required if logging from a tight loop,
// to keep the UI responsive
Application.DoEvents();
}
}
}
public void Write(string line, Color color)
{
lock (_lock)
{
_queue.Enqueue(new Tuple<string, Color>(line, color));
// prevent re-entracy
if (_processing)
return; // DequeueMessages is already in progress
}
if (Thread.CurrentThread.ManagedThreadId == _threadId)
DequeueMessages();
else
_context.Post((_) =>
{
DequeueMessages();
}, null);
}
public static TraceView CreateOnOwnThread()
{
TraceView view = null;
using (var sync = new ManualResetEventSlim())
{
// create it on its own thread
var thread = new Thread(() =>
{
SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
view = new TraceView(null);
view.Show();
sync.Set(); // ready Write calls
Application.Run(view); // view does Application.ExitThread() when closed
return;
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
sync.Wait();
}
return view;
}
}
}
I have a lot more .Net experience with VB than C#, but doesn't the following code:
if (uiTrace.InvokeRequired && !uiTrace.Disposing && !uiTrace.IsDisposed)
{
uiTrace.BeginInvoke(gui);
return;
}
gui();
result in gui being invoked if InvokeRequired etc. in the If statement, as well as being executed (again) in the current (presumably non-UI) thread in gui().
Wouldn't:
If (uiTrace.InvokeRequired && !uiTrace.Disposing && !uiTrace.IsDisposed)
{
uiTrace.BeginInvoke(gui);
return;
}
Else
gui();
be more appropriate?