Not Responding - C# application crashes without any error message - c#

I made a C# application which iterates through an image folder in order to classify the images, The problem is that during the execution. After it classified some images, the program crashes without any error, it just freezes with no error message.
private void ImageProcessing(string filename)
{
panel1.Controls.Clear();
pictureBox1.Image = new Bitmap(filename);
var predictions = _logic.Classify(filename);
foreach (var item in predictions)
{
if (this.InvokeRequired)
{
this.BeginInvoke((MethodInvoker)delegate ()
{
new Label() { Text = $"{item.Label} : {item.Confidence}", Parent = panel1, Dock = DockStyle.Top };
});
}
else
{
new Label() { Text = $"{item.Label} : {item.Confidence}", Parent = panel1, Dock = DockStyle.Top };
}
}
this.CompareScores(predictions);
var biggestCompare = this.CompareScores(predictions);
switch (biggestCompare.Label)
{
case "Has no Lighter":
TurnON_NOK_Lamp();
break;
case "Has Lighter":
TurnOn_OK_Lamp();
break;
}
}
private void Start()
{
Thread.Sleep(1000);
if (filenames.Length == 0)
{
NoFileLabel.Visible = false;
this.TurnOFF_LightTower();
}
else
{
foreach (string filename in filenames)
{
ImageProcessing(filename);
Thread.Sleep(1000);
this.TurnOFF_LightTower();
pictureBox1.Image = null;
pictureBox1.Invalidate();
}
foreach (string filename in filenames)
{
File.Delete(filename);
}
}
}

Apparently you are calling Start() and hence ImageProcessing() methods from UI thread. If there are any long-running calculations (and I believe there are some) then UI will become unresponsive, because you are blocking message processing loop for a long time and all UI is based on this message processing.
I recommend using switching to async methods for any long-running calculations.
Check https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/task-based-asynchronous-programming for detailed information.
In your case the ImageProcessing() and Classify() methods could look like:
private async Task ImageProcessingAsync(string filename)
{
pictureBox1.Image = new Bitmap(filename);
var predictions = await _logic.ClassifyAsync(filename);
foreach (var item in predictions)
{
label1.Text = $"{item.Label} : {item.Confidence}";
}
//await CompareScoresAsync(predictions);
var biggestCompare = this.CompareScoresAsync(predictions);
switch (biggestCompare.Label)
{
case "Has no Lighter":
TurnON_NOK_Lamp();
break;
case "Has Lighter":
TurnOn_OK_Lamp();
break;
}
}
public Task<List<Prediction>> ClassifyAsync(string filename)
{
return Task.Run(() =>
{
// TODO: prepare the result
return result;
});
}
Also you don't need to create a new label every time, so you could put a label once in the designer and then only change its Text.
Thread.Sleep() also is not desirable since it's blocking the executing thread and UI again becomes unresponsive. You could use await Task.Delay() instead.

Related

c# win app forms detect the number of faces on image and prompt error message if there is more than 1 face

I am currently doing an ID pic uploading system using C# Windows App Forms and I would like to allow the user to upload an image and the image must only contain 1 front face. To prevent user from uploading more than 1 face, I would like to prompt them an error message once the system detects more than one face in an image but I am not sure how to go about it. I used takuya takeuchi's dlibdotnet library.
Here is my current code.
namespace DetectTrial
{
public partial class Form1 : Form
{
#region Fields
private readonly BackgroundWorker _BackgroundWorker;
#endregion
#region Constructors
public Form1()
{
this.InitializeComponent();
this._BackgroundWorker = new BackgroundWorker();
this._BackgroundWorker.DoWork += this.BackgroundWorkerOnDoWork;
}
#endregion
#region Methods
#region Event Handlers
private void BackgroundWorkerOnDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
{
var path = doWorkEventArgs.Argument as string;
if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
return;
using (var faceDetector = Dlib.GetFrontalFaceDetector())
using (var ms = new MemoryStream(File.ReadAllBytes(path)))
using (var bitmap = (Bitmap)Image.FromStream(ms))
{
using (var image = bitmap.ToArray2D<RgbPixel>())
{
var dets = faceDetector.Operator(image);
foreach (var g in dets)
Dlib.DrawRectangle(image, g, new RgbPixel { Green = 255 }, thickness: 10);
var result = image.ToBitmap();
this.pictureBox1.Invoke(new Action(() =>
{
this.pictureBox1.Image?.Dispose();
this.pictureBox1.Image = result;
}));
}
}
}
private void button1_Click(object sender, EventArgs e)
{
using (var opnfd = new OpenFileDialog())
{
opnfd.Filter = "Image Files (*.jpg;*.jpeg;*.png;)|*.jpg;*.jpeg;*.png";
if (opnfd.ShowDialog(this) == DialogResult.OK)
{
this._BackgroundWorker.RunWorkerAsync(opnfd.FileName);
}
}
}
#endregion
#endregion
}
}
I don't know where to go from here.
I'm not familiar with the library you're using, but if dets is a collection of detected face rectangles, you can probably use something like this:
var dets = faceDetector.Operator(image);
if (dets.Count() > 1)
{
MessageBox.Show("Too many faces! Why are there so many faces? I can't look. Please make it stop.");
return;
}
else
{
var g = dets.First();
Dlib.DrawRectangle(image, g, new RgbPixel { Green = 255 }, thickness: 10);
var result = image.ToBitmap();
this.pictureBox1.Invoke(new Action(() =>
{
this.pictureBox1.Image?.Dispose();
this.pictureBox1.Image = result;
}));
}
Note that Count() and First() are extension methods from System.Linq so you'll need to make sure there's a using System.Linq; directive at the top of your code file.
Also, the Invoke code is probably better moved to the BackgroundWorker's OnRunWorkerCompleted event (where the cross-thread invoke will no longer be needed) and you can access the PictureBox directly.

C# Manually stopping an asynchronous for-statement (typewriter effect)

I'm making a retro-style game with C# .NET-Framework, and for dialogue I'm using a for-statement, that prints my text letter by letter (like a typewriter-effect):
I'm working with different scenes, and I have a skip button (bottom right) that skips the current dialogue and passes to the next scene. My typewriter-effect automatically stops when all the text is displayed, but when I click on the skip button, it automatically skips to the next scene.
I would like it, when the typewriter is still active, and if I click on the skip button, that it first shows all the text, instead of skipping to the next scene.
So that it only skips to the next scene when all the text is displayed (automatically or manually).
This is the (working code) that I'm using for my typewriter method (+ variables):
public string FullTextBottom;
public string CurrentTextBottom = "";
public bool IsActive;
public async void TypeWriterEffectBottom()
{
if(this.BackgroundImage != null) // only runs on backgrounds that arent black
{
for(i=0; i < FullTextBottom.Length + 1; i++)
{
CurrentTextBottom = FullTextBottom.Substring(0, i); // updating current string with one extra letter
LblTextBottom.Text = CurrentTextBottom; // "temporarily place string in text box"
await Task.Delay(30); // wait for next update
#region checks for IsActive // for debugging only!
if(i < FullTextBottom.Length + 1)
{
IsActive = true;
Debug1.Text = "IsActive = " + IsActive.ToString();
}
if(CurrentTextBottom.Length == FullTextBottom.Length)
{
IsActive = false;
Debug1.Text = "IsActive = " + IsActive.ToString();
}
#endregion
}
}
}
And this is the code that I want to get for my skip button (named Pb_FastForward):
private void PbFastForward_Click(object sender, EventArgs e)
{
if( //typewriter is active)
{
//print all text into the textbox
}
else if( //all text is printed)
{
// skip to the next scene
}
}
But I don't know how to formulate the 2nd part of code. I've tried many different approaches, like using counters that increase on a buttonclick (and using that to check in an if-statement), and many different types of if-statements to see if the typewriter is still active or not, but I haven't got anything to work yet.
Edit
This is the sequence in which different components need to be loaded (on button click), which is related to the way different variables are updated:
Gamestate_Cycle() --> called for loading new scene.
FullTextBottom = LblTextBottom.Text --> called to refresh variables for typewriter.
TypeWriterEffectBottom() --> called to perform typewriter effect.
Avoid async void. Otherwise you can get an Exception that will break your game and you will not able to catch it.
Then use as less global variables in async methods as possible.
I suggest CancellationTokenSource as thread-safe way to stop the Type Writer.
public async Task TypeWriterEffectBottom(string text, CancellationToken token)
{
if (this.BackgroundImage != null)
{
Debug1.Text = "TypeWriter is active";
StringBuilder sb = new StringBuilder(text.Length);
try
{
foreach (char c in text)
{
LblTextBottom.Text = sb.Append(c).ToString();
await Task.Delay(30, token);
}
}
catch (OperationCanceledException)
{
LblTextBottom.Text = text;
}
Debug1.Text = "TypeWriter is finished";
}
}
Define CTS. It's thread-safe, so it's ok to have it in global scope.
private CancellationTokenSource cts = null;
Call TypeWriter from async method to be able to await it.
// set button layout as "Skip text" here
using (cts = new CancellationTokenSource())
{
await TypeWriterEffectBottom(yourString, cts.Token);
}
cts = null;
// set button layout as "Go to the next scene" here
And finally
private void PbFastForward_Click(object sender, EventArgs e)
{
if (cts != null)
{
cts?.Cancel();
}
else
{
// go to the next scene
}
}
I pondered on your task a bit more and it occurred to me that it is a good job for the Rx.Net library.
An advantage of this approach is that you have less mutable state to care about and you almost don't need to think about threads, synchronization, etc.; you manipulate higher-level building blocks instead: observables, subscriptions.
I extended the task a bit to better illustrate Rx capabilities:
there are two pieces of animated text, each one can be fast-forwarded separately;
the user can fast-forward to the final state;
the user can reset the animation state.
Here is the form code (C# 8, System.Reactive.Linq v4.4.1):
private enum DialogState
{
NpcSpeaking,
PlayerSpeaking,
EverythingShown
}
private enum EventKind
{
AnimationFinished,
Skip,
SkipToEnd
}
DialogState _state;
private readonly Subject<DialogState> _stateChanges = new Subject<DialogState>();
Dictionary<DialogState, (string, Label)> _lines;
IDisposable _eventsSubscription;
IDisposable _animationSubscription;
public Form1()
{
InitializeComponent();
_lines = new Dictionary<DialogState, (string, Label)>
{
{ DialogState.NpcSpeaking, ("NPC speaking...", lblNpc) },
{ DialogState.PlayerSpeaking, ("Player speaking...", lblCharacter) },
};
// tick = 1,2...
IObservable<long> tick = Observable
.Interval(TimeSpan.FromSeconds(0.15))
.ObserveOn(this)
.StartWith(-1)
.Select(x => x + 2);
IObservable<EventPattern<object>> fastForwardClicks = Observable.FromEventPattern(
h => btnFastForward.Click += h,
h => btnFastForward.Click -= h);
IObservable<EventPattern<object>> skipToEndClicks = Observable.FromEventPattern(
h => btnSkipToEnd.Click += h,
h => btnSkipToEnd.Click -= h);
// On each state change animationFarames starts from scratch: 1,2...
IObservable<long> animationFarames = _stateChanges
.Select(
s => Observable.If(() => _lines.ContainsKey(s), tick.TakeUntil(_stateChanges)))
.Switch();
var animationFinished = new Subject<int>();
_animationSubscription = animationFarames.Subscribe(frame =>
{
(string line, Label lbl) = _lines[_state];
if (frame > line.Length)
{
animationFinished.OnNext(default);
return;
}
lbl.Text = line.Substring(0, (int)frame);
});
IObservable<EventKind> events = Observable.Merge(
skipToEndClicks.Select(_ => EventKind.SkipToEnd),
fastForwardClicks.Select(_ => EventKind.Skip),
animationFinished.Select(_ => EventKind.AnimationFinished));
_eventsSubscription = events.Subscribe(e =>
{
DialogState prev = _state;
_state = prev switch
{
DialogState.NpcSpeaking => WhenSpeaking(e, DialogState.PlayerSpeaking),
DialogState.PlayerSpeaking => WhenSpeaking(e, DialogState.EverythingShown),
DialogState.EverythingShown => WhenEverythingShown(e)
};
_stateChanges.OnNext(_state);
});
Reset();
}
private DialogState WhenEverythingShown(EventKind _)
{
Close();
return _state;
}
private DialogState WhenSpeaking(EventKind e, DialogState next)
{
switch (e)
{
case EventKind.AnimationFinished:
case EventKind.Skip:
{
(string l, Label lbl) = _lines[_state];
lbl.Text = l;
return next;
}
case EventKind.SkipToEnd:
{
ShowFinalState();
return DialogState.EverythingShown;
}
default:
throw new NotSupportedException($"Unknown event '{e}'.");
}
}
private void ShowFinalState()
{
foreach ((string l, Label lbl) in _lines.Values)
{
lbl.Text = l;
}
}
private void Reset()
{
foreach ((_, Label lbl) in _lines.Values)
{
lbl.Text = "";
}
_state = DialogState.NpcSpeaking;
_stateChanges.OnNext(_state);
}
protected override void OnClosed(EventArgs e)
{
_eventsSubscription?.Dispose();
_animationSubscription?.Dispose();
base.OnClosed(e);
}
private void btnReset_Click(object sender, EventArgs e)
{
Reset();
}
I adjusted your code a little bit to achieve your goal. I'm not sure it's the best way to do it, but it should work.
public async void TypeWriterEffectBottom()
{
if(this.BackgroundImage == null)
{
return;
}
IsActive = true;
for(i=0; i < FullTextBottom.Length && IsActive; i++)
{
CurrentTextBottom = FullTextBottom.Substring(0, i+1);
LblTextBottom.Text = CurrentTextBottom;
await Task.Delay(30);
Debug1.Text = "IsActive = " + IsActive.ToString();
}
IsActive = false;
}
private void PbFastForward_Click(object sender, EventArgs e)
{
if(IsActive)
{
LblTextBottom.Text = FullTextBottom;
IsActive = false;
return;
}
// IsActive == false means all text is printed
// skip to the next scene
}
UPD: Just noticed that Hans Kesting has suggested pretty much exactly this in his comment.
You write what skip / forward button does, so you control it. Just have a check if the length of written text is equal to text that supposed to be written and if yes move as usual if not just display the text in full have delay to be read and move on

Render to winforms picturebox over and over again

I have some bugs that need fixing, one of them involves an out of memory error.
Does anyone know how to do this properly? Thanks, I don't want it to be too messy, or too complicated. I just want to treat a new image as a buffer to render another image to (because of positional changes), and do it via a background thread. Not the UI thread (Too slow likely).
I get out of memory errors, and such. Also not able to access members of Form1 from within the thread function (images and the like throw access errors such as "Object already in use")
Here is my code:
System.Threading.Thread t;
public Image b;
public Bitmap c;
public Bitmap d;
public Bitmap e;
public Bitmap bg;
public Bitmap spr;
int spritex = 0;
int spritey = 0;
int spritedir = 1;
public Form1()
{
InitializeComponent();
Text = "Escape The Hypno Mansion!!".ToString();
t = new System.Threading.Thread(DoThisAllTheTime);
t.Start();
textBox1.Text = "Press Begin button to start!";
pictureBox1.Image = Image.FromFile(#"Images\introgirl.jpg");
b = new Bitmap(#"Images\introgirl.jpg");
c = new Bitmap(#"Images\sprite.png");
var graphics = Graphics.FromImage(b);
Pen blackpen = new Pen(Color.Black, 3);
graphics.DrawLine(blackpen, 0, 0, 100, 100);
graphics.DrawImage(c, new Point(500, 500));
pictureBox1.Image = b;
//pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
}
public void DoThisAllTheTime()
{
while (true)
{
Point p = new Point(spritex, spritey);
bg = new Bitmap(#"Images\test.bmp");
spr = new Bitmap(#"Images\sprite.png");
using (var graphics = Graphics.FromImage(bg))
{
graphics.DrawImage(spr, p);
}
if (pictureBox1.Image != null)
{
pictureBox1.Image.Dispose();
}
pictureBox1.Image = bg;
pictureBox1.Invalidate();
if (spritedir == 1) { spritex += 5; }
if (spritedir == 2) { spritex -= 5; }
if (spritex < 0) { spritex = 0; spritedir = 1; }
if (spritex > 700) { spritex = 700; spritedir = 2; }
}
}
The reason you can't change the image in your picturebox is because the thread that created the image is not the thread that created the picturebox.
In a debugger you can check this by asking the picturebox for InvokeRequired (function Control.IsInvokeRequired) just before changing the function.
So let's rewrite your function and show that modern classes Like Task are much easier to use the your thread.
I'll start your task when the form is loading, and try to stop it when the form is closing.
private Task myTask = null;
private CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
private void OnFormLoading(object sender, EventArgs e)
{
// Start the task
this.myTask = Task.Run( () => DoMyWork(this.cancellationTokenSource.Token));
}
private void OnFormClosing(object sender, FormClosingEventArgs e)
{
// if the Task is still running, ask it to stop itself:
if (myTask != null && !myTask.IsCompleted)
{ // ask the task to stop and wait until it is completed:
this.cancellationTokenSource.Cancel();
// all Tokens extractes from this source will get state CancellationRequested
// wait maximum 5 seconds until the task is completed:
this.UseWaitCursor = true;
this.myTask.Wait(TimeSpan.FromSeconds(5));
this.UseWaitCursor = false;
// cancel closing if the task is still not completed
e.Cancel = !this.myTask.Completed;
}
}
Now the function DoMyWork:
private void DoMyWork(CancellationToken cancellationToken)
{
// Do the same as in your DoThisAllTheTime
// except that you regularly check cancellationToken.IsCancelRequested:
while(!cancellationToken.IsCancelRequested)
{
// calculate the image to display
var imageToDisplay = ...
this.DisplayImage(imageToDisplay);
}
}
void DisplayImage(Image imageToDisplay)
{
if (this.pictureBox1.InvokeRequired)
{
this.Invoke(new MethodInvoker( () => this.DisplayImage(imageToDisplay)));
}
else
{
this.PictureBox1.Image = imageToDisplay;
}
}
See:
How to cancel a Task and its children
Use InvokeRequired with lambda expression
Dispose every disposable instances before the loop ends. Your memory leak is related with disposable items not being cleaned from memory, so you'll eventually run out of memory in your infinite loop.
At the very least, you'll want to dispose both bitmaps at the end of the loop:
bg = new Bitmap(#"Images\test.bmp");
spr = new Bitmap(#"Images\sprite.png");

c# How to change/access WinForms controls from a different class

So I'm trying to change the text from a WinForms project, from another class than the Form class.
It should work like this:
But instead it does this:
The way I used to do it is pass along the object as a parameter to my other class and from that other class I could change the text. I do the same with the progressbar and it does work there, so it's weird that it works with the progressbar but not the label.
I use this method to change the progressbar:
public void IncreaseProgress(int progBarStepSize, String statusMsg, int currentProject=-1) {
if (currentProject != -1) {
lblStatus.Text = String.Format("Status: {0} | project {1} of {2}",statusMsg,currentProject,ProjectCount);
}
else {
lblStatus.Text = String.Format("Status: {0}",statusMsg);
}
pb.Increment(progBarStepSize);
}
And here is where I use the method:
public void InitialiseFile(List<string> filePaths, int eurojobType)
{
foreach (string sheet in outputSheets) {
switch (sheet) {
case "Summary":
for (int i = 0; i < filePaths.Count; i++) {
var filePath = filePaths[i];
IncreaseProgress(1, "Reading Summary", i);
worksheetIn = excelReader.ReadExcelSummary(filePath);
IncreaseProgress(1, "Writing Summary", i);
excelWriter.WriteExcelSummary(worksheetIn);
}
break;
case "Monthly_Cat1":
for (int i = 0; i < filePaths.Count; i++) {
var filePath = filePaths[i];
IncreaseProgress(1, "Reading Monthly", i);
worksheetIn = excelReader.ReadExcelMonthly(filePath);
IncreaseProgress(1, "Writing Monthly", i);
excelWriter.WriteExcelMonthly(worksheetIn);
}
break;
}
}
IncreaseProgress(1, "Completed!");
}
Now I know this code works because the progressbar increments. And it should jump in the first if-loop because i gets passed as a parameter, which is never -1.
//manager class
private Label lblStatus;
private ProgressBar pb;
public Manager(ProgressBar pb, Label lbl){
this.pb = pb;
lblStatus = lbl;
}
//Form class
Manager mgr = new Manager(progressBar1, lblStatus, projectFilePaths.Count, outputSheets.ToArray(), exportPath);
mgr.InitialiseFile(projectFilePaths, eurjobType);
You can call lblStatus.Refresh(); to force the control to be redrawn, after setting its Text.
But consider Slaks comment:
Don't do blocking work on the UI thread
You can consider using BackgroundWorker or Task.Run or async/await pattern instead.
As an example:
private async void button1_Click(object sender, EventArgs e)
{
await Task.Run(() =>
{
for (int i = 0; i < 10000; i++)
{
this.Invoke(new Action(() =>
{
label1.Text = i.ToString();
label1.Refresh();
}));
}
});
}
This way the numbers increase, the label refreshes and shows the new value, while the UI is responsive and for example you can move your form or click on other button.
You should put your UI related codes in an action fired by Invoke to prevent receiving cross thread operation exception.

Logging Window in MTA thread: Access Violation

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?

Categories