For the m_listeners
BlockingCollection<>, I know it's a bit hacky right now, but I'm not sure what to use instead (List<>, Dictionary<>, something else?). I want to be able to add/remove listeners at the same time as I may be broadcasting to those listeners without throwing null reference exceptions. For example add a listener for a TextBox
when I open a sub-form and remove the listener when I close the sub-form without throwing an error because the TextBox
doesn't exist or the delegate has become null, etc.
WindowsFormsApplication1 containing:
- Form1, register OnLoad event
- button1, register OnClick event
- button2, register OnClick event
- textbox1, enable multiline property and expand to show several lines at a time
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using System.IO;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
//Fields
private volatile bool cancel1 = false;
private volatile bool cancel2 = false;
private volatile bool cancel3 = false;
private volatile bool cancel4 = false;
public Form1()
{
InitializeComponent();
}
//Event Handlers
private void button1_Click(object sender, EventArgs e)
{
//Automated Producers
if (!cancel1)
{
Task.Factory.StartNew(() =>
{
int count = 0;
while (!cancel1)
{
Log.Append("File", "log to file " + count++);
Thread.Sleep(100);
}
cancel1 = false;
});
}
if (!cancel2)
{
Task.Factory.StartNew(() =>
{
int count = 0;
while (!cancel2)
{
Log.Append("GUI", "log to GUI " + count++);
Thread.Sleep(200);
}
cancel2 = false;
});
}
if (!cancel3)
{
Task.Factory.StartNew(() =>
{
int count = 0;
while (!cancel3)
{
Log.Append("Error", "log to Error " + count++);
Thread.Sleep(300);
}
cancel3 = false;
});
}
if (!cancel4)
{
Task.Factory.StartNew(() =>
{
int count = 0;
while (!cancel4)
{
Log.Append("", "log to console " + count++);
Thread.Sleep(400);
}
cancel4 = false;
});
}
}
private void button2_Click(object sender, EventArgs e)
{
//Cancel Producers
cancel1 = true;
cancel2 = true;
cancel3 = true;
cancel4 = true;
}
private void Form1_Load(object sender, EventArgs e)
{
//Delete Old Files
string LogFile = Directory.GetCurrentDirectory() + "\\Log.txt";
if (File.Exists(LogFile))
File.Delete(LogFile);
string VerboseLog = Directory.GetCurrentDirectory() + "\\VerboseLog.txt";
if (File.Exists(VerboseLog))
File.Delete(VerboseLog);
//Add Consumer Callback methods to Logger class
//Append to File
Log.RegisterWriter(
new Action<string, string>((tag, entry) =>
{
if (tag == "File")
{
using (TextWriter Stream = new StreamWriter(LogFile, true))
{
Stream.WriteLine(entry);
}
}
}));
//Append to different file
Log.RegisterWriter(
new Action<string, string>((tag, entry) =>
{
using (TextWriter Stream = new StreamWriter(VerboseLog, true))
{
Stream.WriteLine(DateTime.Now + ":\t" + entry);
}
}));
//Append to Console
Log.RegisterWriter(
new Action<string, string>((tag, entry) =>
{
Console.WriteLine(entry);
}));
//Append to multiline textBox1
Log.RegisterWriter(
new Action<string, string>((tag, entry) =>
{
if (tag == "GUI")
{
entry += "\r\n";
if (this.InvokeRequired)
{
this.BeginInvoke(new Action<string>(textBox1.AppendText), new object[] { entry });
return;
}
else
{
//Under the circumstances, this never occurs. An invoke is always required.
textBox1.AppendText(entry);
return;
}
}
}));
}
}
public static class Log
{
//Fields
private static BlockingCollection<Tuple<string, string>> m_logItems;
private static BlockingCollection<Action<string, string>> m_listeners;
//Methods
public static void Append(string p_tag, string p_text)
{
//Add Log Entry
m_logItems.Add(new Tuple<string, string>(p_tag, p_text));
}
public static void RegisterWriter(Action<string, string> p_callback)
{
//Add callback method to list
m_listeners.Add(p_callback);
}
//Constructor
static Log()
{
//Init Blocking Lists
m_listeners = new BlockingCollection<Action<string, string>>();
m_logItems = new BlockingCollection<Tuple<string, string>>();
//Begin Log Entry Consumer Task
Task.Factory.StartNew(() =>
{
//Consume as Log Entries are added to the collection
foreach (var logentry in m_logItems.GetConsumingEnumerable())
{
//Broadcast to each listener
foreach (var callback in m_listeners)
{
callback(logentry.Item1, logentry.Item2);
}
}
});
}
}
}
I didn't want to use a third-party logging library (partially to understand these mechanisms better) and just wanted to create a very simple way to post items from pretty much anywhere in my code (any thread) to various outputs (files, textboxes, the console, etc.).
The frequency of logging in the real application is much much slower than demonstrated here (i.e. it should never produce more than can be consumed under real-world conditions).
m_listeners
as it has no remove method that lets me unregister a specific item. I don't know what can be used instead that is still thread-safe. \$\endgroup\$lock
with a private locking object wrapping access to the underlying collection. \$\endgroup\$event
fromLog
, and just fire it whenever someone logs something? You don't need any 'thread safety' to do that - justinvoke
in the handler. \$\endgroup\$Controls
in the Log class. I've never implemented theISynchronizeInvoke
interface, so I'm not sure that's any easier at this point. \$\endgroup\$