Note that there are some explanatory texts on larger screens.

plurals
  1. POSimple ThreadSafe Log Class
    primarykey
    data
    text
    <ol> <li><p>I'm wondering if I've used these components all correctly/efficiently. Specifically writing to files I'm not sure is thread-safe though it didn't throw any errors in this test project (even with no sleeping). I can't tell if this is because the broadcaster is already synchronized and therefore file-writes are subsequently synchronized as well... ?</p></li> <li><p>I wasn't sure if I could use a normal List&lt;> or some other collection instead of the BlockingCollection&lt;> for the <code>m_listeners</code>. I can add listeners cleanly enough at the start of execution, but I think I've limited myself if I want to remove them later. I may for example want to add/remove listeners more dynamically in the future such as adding a TextBox listener on showing a child form and removing it on closing.</p></li> </ol> <p>WindowsFormsApplication1 containing:</p> <ul> <li>Form1, register OnLoad event</li> <li>button1, register OnClick event</li> <li>button2, register OnClick event</li> <li>textbox1, enable multiline property and expand to show several lines at a time</li> </ul> <pre class="lang-cs prettyprint-override"><code>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(() =&gt; { int count = 0; while (!cancel1) { Log.Append("File", "log to file " + count++); Thread.Sleep(1); } cancel1 = false; }); } if (!cancel2) { Task.Factory.StartNew(() =&gt; { int count = 0; while (!cancel2) { Log.Append("GUI", "log to GUI " + count++); Thread.Sleep(2); } cancel2 = false; }); } if (!cancel3) { Task.Factory.StartNew(() =&gt; { int count = 0; while (!cancel3) { Log.Append("Error", "log to Error " + count++); Thread.Sleep(3); } cancel3 = false; }); } if (!cancel4) { Task.Factory.StartNew(() =&gt; { int count = 0; while (!cancel4) { Log.Append("", "log to console " + count++); Thread.Sleep(4); } 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&lt;string, string&gt;((tag, entry) =&gt; { if (tag == "File") { using (TextWriter Stream = new StreamWriter(LogFile, true)) { Stream.WriteLine(entry); } } })); //Append to different file Log.RegisterWriter( new Action&lt;string, string&gt;((tag, entry) =&gt; { using (TextWriter Stream = new StreamWriter(VerboseLog, true)) { Stream.WriteLine(DateTime.Now + ":\t" + entry); } })); //Append to Console Log.RegisterWriter( new Action&lt;string, string&gt;((tag, entry) =&gt; { Console.WriteLine(entry); })); //Append to multiline textBox1 Log.RegisterWriter( new Action&lt;string, string&gt;((tag, entry) =&gt; { if (tag == "GUI") { entry += "\r\n"; if (this.InvokeRequired) { this.BeginInvoke(new Action&lt;string&gt;(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&lt;Tuple&lt;string, string&gt;&gt; m_logItems; private static BlockingCollection&lt;Action&lt;string, string&gt;&gt; m_listeners; //Methods public static void Append(string p_tag, string p_text) { //Add Log Entry m_logItems.Add(new Tuple&lt;string, string&gt;(p_tag, p_text)); } public static void RegisterWriter(Action&lt;string, string&gt; p_callback) { //Add callback method to list m_listeners.Add(p_callback); } //Constructor static Log() { //Init Blocking Lists m_listeners = new BlockingCollection&lt;Action&lt;string, string&gt;&gt;(); m_logItems = new BlockingCollection&lt;Tuple&lt;string, string&gt;&gt;(); //Begin Log Entry Consumer Task Task.Factory.StartNew(() =&gt; { //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); } } }); } } } </code></pre> <p>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.).</p> <p>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).</p>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload