I've got a case that might be useful to analyze and extract some conclusions.
I've got a class that implements ITaskWorker, and each Task can run simultaneously with other Task connected with a scheduling engine.
Suppose Task A runs a job for object A_1 with B1...BN attributes, while for each attribute a command line runs and gives results (which is blocked until an answer is recieved from the command line process).
This means that for Task B we can schedule the same A_1 with B1...BN attributes.
For the following piece of code and explanation, could you find something that might resolve in threads interrupting each other (deadlocks, race conditions, starvation)?
How can I ensure that there isn't a multi threaded issue here?
I think starvation cannot be an issue here, unless there are a lot of tasks of the same type that other types cannot get to be done (see below explanation about the code). I don't see a case for deadlock, but there might be a race condition on mainLocaker or connectionLockers data members (because of the same variable and collection that's are used across multiple methods).
There cannot be the same key in the dictionary (I've verified that: [b_i.A_Name + "_" + b_i.B_Name] creates a unique key)
I've got this code in C#. Please notice that mainLocker and connectorsLockers is being used in several methods like doTaskOfTypeX, so several 'types' of workers might lock it in different parts of code:
private static object mainLocker = new Object();
private static Dictionary<string, object> connectionLockers = new Dictionary<string,object>();
private doTaskOfTypeA()
{
// ... initialize A from task parameters
var attibutes = getListOfAttribuesByObject(A);
bool localLocalTaken = false;
foreach (B b_i in attibutes)
{
try{
lock (mainLocker)
{
if (!typeLockers.ContainsKey(b_i.A_Name + "_" + b_i.B_Name))
{
typeLockers.Add(b_i.A_Name + "_" + b_i.B_Name, new object());
}
}
localLocalTaken = false;
Monitor.Enter(connectionLockers[b_i.A_Name + "_" + b_i.B_Name, ref localLocalTaken);
if (localLocalTaken)
{
var calcObj = callCLIProcess(); // a CMD call is in here
if (calcObj != null)
{
// do things with calcObj
}
else
{
jobResult = new ScheduleTaskResult(ResultTypes.Failed);
}
}
}
catch
{
jobResult = new ScheduleTaskResult(ResultTypes.Failed);
throw;
}
finally
{
if (localLocalTaken)
{
Monitor.Exit(connectionLockers[b_i.A_Name + "_" + b_i.B_Name]);
}
}
}
}
Actually, there is no issue here.
The [// do things with calcObj] notation had a code from an external library that didn't work too well :-)
Related
one of the threads in my application blocked at the following lock statement and resulted in a deadlock
void ExecuteCommand()
{
lock(this._lockinstance)
{
// do some operation
}
}
Is it possible to easily identify which thread is currently holding the lock?.. My application has more than 50 threads, which makes it difficult to go through each callstack using visual studio to locate the thread that holds the lock
Some sample code to try out:
class Test {
private object locker = new object();
public void Run() {
lock (locker) { // <== breakpoint here
Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);
}
}
}
Set a breakpoint on the indicated line. When it breaks, use Debug + Windows + Memory + Memory 1. Right click the window and choose "4-byte Integer". In the Address box, type &locker. The 2nd word is the thread ID of the thread that owns the lock. Step past the lock statement to see it change.
Beware that the number is the managed thread ID, not the operating system thread ID that you see in the Debug + Windows + Threads window. That kinda sucks, you probably should add some logging to your program that dumps the value of ManagedThreadId so you have a way to match the value to a thread. Update: fixed in later VS versions, the Debug > Windows > Threads debugger window now shows the ManagedThreadId.
Recently I was trying to determine what function was holding a lock and found the following very useful and had not seen in demonstrated anywhere before. I've placed it as an answer here in case others find it useful too.
Many of the other solutions posted earlier require writing a new class and then converting of all lock(blah) to BetterLock(blah) which is a lot of work for debugging and which you may not want in the production/shipped version of your code. Others required having the debugger attached which changes the code's timing and could obscure the issue.
Instead, try the following...
Original code:
object obj = new object();
lock(obj)
{
// Do stuff
}
Modified code for debugging:
object _obj = new object();
object obj
{
get
{
System.Diagnostics.StackFrame frame = new System.Diagnostics.StackFrame(1);
System.Diagnostics.Trace.WriteLine(String.Format("Lock acquired by: {0} on thread {1}", frame.GetMethod().Name, System.Threading.Thread.CurrentThread.ManagedThreadId));
return _obj;
}
}
// Note that the code within lock(obj) and the lock itself remain unchanged.
lock(obj)
{
// Do stuff
}
By exposing obj as a property, at least temporarily, with very minimal code changes you can determine what function acquired the lock last and on what thread - just look at the Trace output for the last entry. Of course you can output any other information you might find useful in the getter as well.
No, this will not let you determine when a lock was released, but if it was getting released in a timely fashion, then you didn't actually have a lock contention issue in the first place.
You can implement a Monitor wrapper that saves stack traces & thread names on enter.
Old way:
private object myLock = new object();
...
lock(myLock)
{
DoSomething();
}
...
With code below:
private SmartLock myLock = new SmartLock();
...
myLock.Lock( () =>
{
DoSomething();
}
);
...
Source:
public class SmartLock
{
private object LockObject = new object();
private string HoldingTrace = "";
private static int WARN_TIMEOUT_MS = 5000; //5 secs
public void Lock(Action action)
{
try
{
Enter();
action.Invoke();
}
catch (Exception ex)
{
Globals.Error("SmartLock Lock action", ex);
}
finally
{
Exit();
}
}
private void Enter()
{
try
{
bool locked = false;
int timeoutMS = 0;
while (!locked)
{
//keep trying to get the lock, and warn if not accessible after timeout
locked = Monitor.TryEnter(LockObject, WARN_TIMEOUT_MS);
if (!locked)
{
timeoutMS += WARN_TIMEOUT_MS;
Globals.Warn("Lock held: " + (timeoutMS / 1000) + " secs by " + HoldingTrace + " requested by " + GetStackTrace());
}
}
//save a stack trace for the code that is holding the lock
HoldingTrace = GetStackTrace();
}
catch (Exception ex)
{
Globals.Error("SmartLock Enter", ex);
}
}
private string GetStackTrace()
{
StackTrace trace = new StackTrace();
string threadID = Thread.CurrentThread.Name ?? "";
return "[" + threadID + "]" + trace.ToString().Replace('\n', '|').Replace("\r", "");
}
private void Exit()
{
try
{
Monitor.Exit(LockObject);
HoldingTrace = "";
}
catch (Exception ex)
{
Globals.Error("SmartLock Exit", ex);
}
}
}
Yes, there is a 'Threads' view that you can use in VS. Break anywhere in your application (or click the 'Break All' button) then you can select each thread and view who has the lock (if anyone).
To add it, go to Debug > Windows > Threads (Ctrl+D,T)
Old posts are old.
But i thought i might give a solution i find to be fairly useful for trying to track down dead locks and other locking problems.
I use a disposable class for my lock - I like Monitor but any locking mechanism could be used.
public class MonitorLock : IDisposable
{
public static MonitorLock CreateLock(object value)
{
return new MonitorLock(value);
}
private readonly object _l;
protected MonitorLock(object l)
{
_l = l;
Console.WriteLine("Lock {0} attempt by {1}", _l, Thread.CurrentThread.ManagedThreadId);
Monitor.Enter(_l);
Console.WriteLine("Lock {0} held by {1}" , _l, Thread.CurrentThread.ManagedThreadId);
}
public void Dispose()
{
Monitor.Exit(_l);
Console.WriteLine("Lock {0} released by {1}", _l, Thread.CurrentThread.ManagedThreadId);
}
}
I use a lock object with a name so I can be clear as to which lock I'm trying to aquire.
public class LockObject
{
public string Name { get; set; }
public LockObject(string name)
{
Name = name;
}
public override string ToString()
{
return Name;
}
}
Finally create a lock object, and then in a using block hold the object.
//create an object to lock on
private readonly object _requestLock = new LockObject("_requestLock");
using (MonitorLock.CreateLock(_requestLock))
{
//do some work
}
Output should be something along the lines of
Lock _requestLock attempt by 92
Lock _requestLock held by 92
Lock _requestLock attempt by 19
Lock _requestLock released by 92
Lock _requestLock held by 19
Lock _requestLock released by 19
Hope that someone finds this useful :)
The Managed Stack Explorer from http://mse.codeplex.com/ or http://www.microsoft.com/downloadS/details.aspx?FamilyID=80cf81f7-d710-47e3-8b95-5a6555a230c2&displaylang=en is excellent in such cases.
It hooks into running managed code (appropriate permissions needed) including live code, and grabs a list of running threads. You can double-click on any of them or (more useful in cases like this) select the lot and hit enter for a quick relatively non-invasive (obviously it's going to consume resources, but it goes in and out as quickly as it can) dump of the current stacks of different threads. Great for finding a deadlock, infinite loop, near-infinite loop (for those times when your application accidentally depends upon astronomers being pessimistic about how long the earth will last to have a hope of completing) and other such cases.
I'm not sure in which version this feature was added, but the Visual Studio 2022 debugger now shows in its Call Stack window the ID of the thread that owns the lock on which another thread is waiting to acquire, e.g.,
I found this over here.
I'm trying to create a Class to manage my Cloud Firestore requests (like any SQLiteHelper Class). However, firebase uses async calls and I'm not able to return a value to other scripts.
Here an example (bool return):
public bool CheckIfIsFullyRegistered(string idUtente)
{
DocumentReference docRef = db.Collection("Utenti").Document(idUtente);
docRef.GetSnapshotAsync().ContinueWithOnMainThread(task =>
{
DocumentSnapshot snapshot = task.Result;
if (snapshot.Exists)
{
Debug.Log(String.Format("Document {0} exist!", snapshot.Id));
return true; //Error here
}
else
{
Debug.Log(String.Format("Document {0} does not exist!", snapshot.Id));
}
});
}
Unfortunately, since Firestore is acting as a frontend for some slow running I/O (disk access or a web request), any interactions you have with it will need to be asynchronous. You'll also want to avoid blocking your game loop if at all possible while performing this access. That is to say, there won't be a synchronous call to GetSnapshotAsync.
Now there are two options you have for writing code that feels synchronous (if you're like me, it's easier to think like this than with callbacks or reactive structures).
First is that GetSnapshotAsync returns a task. You can opt to await on that task in an async function:
public async bool CheckIfIsFullyRegistered(string idUtente)
{
DocumentReference docRef = db.Collection("Utenti").Document(idUtente);
// this is equivalent to `task.Result` in the continuation code
DocumentSnapshot snapshot = await docRef.GetSnapshotAsync()
return snapshot.Exists;
}
The catch with this is that async/await makes some assumptions about C# object lifecycle that aren't guaranteed in the Unity context (more information in my related blog post and video). If you're a long-time Unity developer, or just want to avoid this == null ever being true, you may opt to wrap your async call in a WaitUntil block:
private IEnumerator CheckIfIsFullyRegisteredInCoroutine() {
string idUtente;
// set idUtente somewhere here
var isFullyRegisteredTask = CheckIfIsFullyRegistered(idUtente);
yield return new WaitUntil(()=>isFullyRegisteredTask.IsComplete);
if (isFullyRegisteredTask.Exception != null) {
// do something with the exception here
yield break;
}
bool isFullyRegistered = isFullyRegisteredTask.Result;
}
One other pattern I like to employ is to use listeners instead of just retrieving a snapshot. I would populate some Unity-side class with whatever the latest data is from Firestore (or RTDB) and have all my Unity objects ping that MonoBehaviour. This fits especially well with Unity's new ECS architecture or any time you're querying your data on a per-frame basis.
I hope that all helps!
This is how i got it to work, but excuse my ignorance as i've only been using FBDB for like a week.
Here is a snippet, I hope it helps someone.
Create a thread task extension to our login event
static Task DI = new System.Threading.Tasks.Task(LoginAnon);
Logging in anon
DI = FirebaseAuth.DefaultInstance.SignInAnonymouslyAsync().ContinueWith(result =>
{
Debug.Log("LOGIN [ID: " + result.Result.UserId + "]");
userID = result.Result.UserId;
FirebaseDatabase.DefaultInstance.GetReference("GlobalMsgs/").ChildAdded += HandleNewsAdded;
FirebaseDatabase.DefaultInstance.GetReference("Users/" + userID + "/infodata/nickname/").ValueChanged += HandleNameChanged;
FirebaseDatabase.DefaultInstance.GetReference("Users/" + userID + "/staticdata/status/").ValueChanged += HandleStatusChanged;
FirebaseDatabase.DefaultInstance.GetReference("Lobbies/").ChildAdded += HandleLobbyAdded;
FirebaseDatabase.DefaultInstance.GetReference("Lobbies/").ChildRemoved += HandleLobbyRemoved;
loggedIn = true;
});
And then get values.
DI.ContinueWith(Task =>
{
FirebaseDatabase.DefaultInstance.GetReference("Lobbies/" + selectedLobbyID + "/players/").GetValueAsync().ContinueWith((result) =>
{
DataSnapshot snap2 = result.Result;
Debug.Log("Their nickname is! -> " + snap2.Child("nickname").Value.ToString());
Debug.Log("Their uID is! -> " + snap2.Key.ToString());
//Add the user ID to the lobby list we have
foreach (List<string> lobbyData in onlineLobbies)
{
Debug.Log("Searching for lobby:" + lobbyData[0]);
if (selectedLobbyID == lobbyData[0].ToString()) //This is the id of the user hosting the lobby
{
Debug.Log("FOUND HOSTS LOBBY ->" + lobbyData[0]);
foreach (DataSnapshot snap3 in snap2.Children)
{
//add the user key to the lobby
lobbyData.Add(snap3.Key.ToString());
Debug.Log("Added " + snap3.Child("nickname").Value.ToString() + " with ID: " + snap3.Key.ToString() + " to local lobby.");
currentUsers++;
}
return;
}
}
});
});
Obviously you can alter it how you like, It does not really need loops but i'm using them to test before i compact the code into something less readable and more intuitive.
I have a C# application where I spawn multiple threads. I'm on .NET framework 4.7.1. Inside these threads, work is performed and this work may execute user-defined scripted functions. I am using ClearScript as the scripting engine and for purposes of this question I am using the VBScriptEngine. Here is an sample application demonstrating my problem:
static VBScriptEngine vbsengine = new VBScriptEngine();
static void Main(string[] args)
{
for (int i=0;i<4000;i++)
{
Thread t = new Thread(Program.ThreadedFunc);
t.Start(i);
}
Console.ReadKey();
}
static void ThreadedFunc(object i)
{
Console.WriteLine(i + ": " + vbsengine.Evaluate("1+1"));
}
On the Evaluate() function I get the following error:
System.InvalidOperationException: 'The calling thread cannot access this object because a different thread owns it.'
I understand ClearScript has implemented thread affinity and a spawned thread cannot access the globally defined engine. So what is my alternative? Create a new instance of ClearScript for each new thread? This seems incredibly wasteful and would create a lot of overhead - my application will need to process thousands of threads. I went ahead and tried this approach anyways - and while it does work (for a while) - end up getting an error. Here's a revised version of my sample app:
static void Main(string[] args)
{
for (int i=0;i<4000;i++)
{
Thread t = new Thread(Program.ThreadedFunc);
t.Start(i);
}
Console.ReadKey();
}
static void ThreadedFunc(object i)
{
using (VBScriptEngine vbsengine = new VBScriptEngine())
{
Console.WriteLine(i + ": " + vbsengine.Evaluate("1+1"));
}
}
On the new VBScriptEngine() call, I now start getting: System.ComponentModel.Win32Exception: 'Not enough storage is available to process this command'.
I'm not really sure what's causing that message as the application isn't taking up a lot of RAM.
I realize this sample application is starting all the threads at once but my full application ensures only 4 threads are running and I still end up getting this message after a while. I don't know why, but I can't get rid of this message either - even after restarting the app and Visual Studio. A little clarity on what's causing this message would be useful.
So my question is - if I only need, say 4 threads, running at once - is there a way I can just create 4 instances of the VBScriptEngine and reuse it for each new thread call? Or even just 1 instance of the VBScriptEngine on the main thread and each new thread just shares it?
With some help from the ClearScript team, I was able to get able to get my sample app to work using only 1 dedicated engine instance per thread. The trick was creating all the necessary engines first with its own thread, then within my loop using Dispatcher.Invoke() to call my threaded function. Here is an updated sample app using this approach:
static void Main(string[] args)
{
var vbengines = new VBScriptEngine[Environment.ProcessorCount];
var checkPoint = new ManualResetEventSlim();
for (var index = 0; index < vbengines.Length; ++index)
{
var thread = new Thread(indexArg =>
{
using (var engine = new VBScriptEngine())
{
vbengines[(int)indexArg] = engine;
checkPoint.Set();
Dispatcher.Run();
}
});
thread.Start(index);
checkPoint.Wait();
checkPoint.Reset();
}
Parallel.ForEach(Enumerable.Range(0, 4000), item => {
var engine = vbengines[item % vbengines.Length];
engine.Dispatcher.Invoke(() => {
ThreadedFunc(new myobj() { vbengine = engine, index = item });
});
});
Array.ForEach(vbengines, engine => engine.Dispatcher.InvokeShutdown());
Console.ReadKey();
}
static void ThreadedFunc(object obj)
{
Console.WriteLine(((myobj)obj).index.ToString() + ": " + ((myobj)obj).vbengine.Evaluate("1+1").ToString());
}
class myobj
{
public VBScriptEngine vbengine { get; set; }
public int index { get; set; }
}
Long time lurker (especially on this problem). I have been studying asynchronous posts on this website directly related to my problem, but I have not found a solution. I have also tried many different variations of the below code to no avail.
What is happening is I asynchronously execute ps.BeginInvoke() after loading commands into it, use some sort of method to launch the invocation asynchronously, and it has indeed always run asynchronously! The problem though is that (for example), the commands executed will only return results sometimes 2 out of 10 times, sometimes 8 out of 10 times. Here is an example of the current code I am executing:
internal class aSyncPowershellCommand
{
//private static Collection<Collection<PSObject>> GlobalResults = new Collection<Collection<PSObject>>();
// This exists simply
public bool Finished { get; set; }
private object instanceLock = new object();
private PowerShell ps;
// private Guid uniqueGuid = Guid.NewGuid();
private PSDataCollection<PSObject> psresult;
public async Task<bool> PowershellLaunchPool(Dictionary<ARS_GroupObject, string> itemListing)
// itemListing contains a custom class that specified details about an ARS group object
// and the String "Value" contains the action (in this case, "get-QADGroup".
{
// Create a common RunspacePool --- it should be noted that for testing purposes, I am sending exactly 8 groups as
// part of the query, for testing purposes.
RunspacePool pool = RunspaceFactory.CreateRunspacePool(1, 8);
pool.CleanupInterval = new TimeSpan(0, 5, 0);
pool.Open();
// Create a spot to track the results in a unique way (I have experimented with lists, dictionaries, etc.... the results are
// always the same though, this just happens to be my latest method to attempt to avoid the "Key already added" error"
ConcurrentDictionary<Guid, IAsyncResult> powershellCommandResults = new ConcurrentDictionary<Guid, IAsyncResult>();
ConcurrentDictionary<Guid, PowerShell> powershellSessions = new ConcurrentDictionary<Guid, PowerShell>();
// Locking the instance, again, not sure if this actually does anything as far as my code is concerned, but it's
// still keeping in line with the same results as before.... semi-success and a lot of "key already added" errors.
lock (instanceLock)
{
foreach (var grpPair in itemListing)
{
// Create a unique GUID for the dictionary, just in case this is what was triggering the "key already added" error
Guid uniqueGuid = Guid.NewGuid();
// This passes text output to a textbox, not important
StatusUpdater.PassPowershellStatus(uniqueGuid.ToString() + " - 1\n");
// Create a fresh PowerShell instance
this.ps = PowerShell.Create();
//StatusUpdater.PassPowershellStatus(this.ps.InstanceId.ToString());
// Add this fresh PowerShell instance to the common RunspacePool
this.ps.RunspacePool = pool;
// Create a fresh instance of psresult to keep track of changes. This is honestly one piece of code I am suspicious
// of as far as throwing the "key already added" error, but I haven't been able to isolate.
this.psresult = new PSDataCollection<PSObject>();
// grpPair.value is "get-QADGroup"
this.ps.Commands.AddCommand(grpPair.Value);
//grpPair.Key.Groupname ---- name of the group
this.ps.Commands.AddParameter("identity", grpPair.Key.GroupName);
// loading up the collection of PowerShell sessions to keep track of, trying as hard as possible to keep this
// all as unique as I possibly can.
powershellSessions.TryAdd(uniqueGuid, this.ps);
// Reference event handlers for when something happens with the variable psResult
this.psresult.DataAdded += new EventHandler<DataAddedEventArgs>(tmpInstance_DataAdded);
this.ps.InvocationStateChanged += new EventHandler<PSInvocationStateChangedEventArgs>(ps_InvocationStateChanged);
}
}
foreach (var execute in powershellSessions)
{
// Looping through all of the PowerSHell sessions queue up and invoking, relying on their same instance of psresult
// to return the results and trigger the tmpInstance_DataAdded and ps_InvocationStateChanged events.
powershellCommandResults.TryAdd(execute.Key, execute.Value.BeginInvoke<PSObject, PSObject>(null, this.psresult));
}
while (Finished != true)
{
Thread.Sleep(3000);
}
return true;
}
private void tmpInstance_DataAdded(object sender, DataAddedEventArgs e)
{
PSDataCollection<PSObject> tmpReturn = (PSDataCollection<PSObject>)sender;
foreach (var item in tmpReturn)
{
StatusUpdater.PassPowershellStatus(item.BaseObject.ToString() + "\n");
}
}
private void ps_InvocationStateChanged(object sender, PSInvocationStateChangedEventArgs e)
{
var psInfo = (PowerShell)sender;
if (e.InvocationStateInfo.State == PSInvocationState.Completed)
{
StatusUpdater.PassPowershellStatus("RESULT --- " + e.InvocationStateInfo.Reason.ToString() + "\n");
psInfo.Stop();
psInfo.Dispose();
psInfo = null;
}
if (e.InvocationStateInfo.State == PSInvocationState.Failed)
{
StatusUpdater.PassPowershellStatus("*** ERROR - \n" + e.InvocationStateInfo.Reason.ToString() + " ***\n");
psInfo.Stop();
psInfo.Dispose();
psInfo = null;
// StatusUpdater.PassPowershellStatus(e.InvocationStateInfo.Reason.ToString());
}
}
}
I have tried different combinations of IASyncResult result = ps.BeginInvoke() and awaiting that command many times, but it has the same results as the code above. Also tried throwing it into a var task = Task.Factory.FromAsync(ps.beginInvoke(), p => ps.EndInvoke(p)); style block and adding to a list of tasks and then awaiting all of their executions with .ConfigureAwait(false);, again... the same results!
So what are the results anyhow? The commands all execute, but on unsuccessful attempts, thee following exception is thrown:
System.Management.Automation.CmdletInvocationException: An item with the same key has already been added.
---> System.ArgumentException: An item with the same key has already been added.
Otherwise, it returns results, and very quickly.
I believe that the problem is the PowerShell session is trying to write to the same internal output object, despite the fact that it is executing as
1.) It's own new PowerShell instance
2.) In a different runspace as managedby the RunSpace pool.
This error occurs at least once every single time I run the script. Sometimes it actually spits out 8 out of 10 group results, and sometimes it only works on 1.
How can I stop this collision from happening? I am suspecting that there is something internally happening within PowerShell that is causing this to happen.
I've made my Logger, that logs a string, a static class with a static
so I can call it from my entire project without having to make an instance of it.
quite nice, but I want to make it run in a separate thread, since accessing the file costs time
is that possible somehow and what's the best way to do it?
Its a bit of a short description, but I hope the idea is clear. if not, please let me know.
Thanks in advance!
By the way any other improvements on my code are welcome as well, I have the feeling not everything is as efficient as it can be:
internal static class MainLogger
{
internal static void LogStringToFile(string logText)
{
DateTime timestamp = DateTime.Now;
string str = timestamp.ToString("dd-MM-yy HH:mm:ss ", CultureInfo.InvariantCulture) + "\t" + logText + "\n";
const string filename = Constants.LOG_FILENAME;
FileInfo fileInfo = new FileInfo(filename);
if (fileInfo.Exists)
{
if (fileInfo.Length > Constants.LOG_FILESIZE)
{
File.Create(filename).Dispose();
}
}
else
{
File.Create(filename).Dispose();
}
int i = 0;
while(true)
{
try
{
using (StreamWriter writer = File.AppendText(filename))
{
writer.WriteLine(str);
}
break;
}
catch (IOException)
{
Thread.Sleep(10);
i++;
if (i >= 8)
{
throw new IOException("Log file \"" + Constants.LOG_FILENAME + "\" not accessible after 5 tries");
}
}
}
}
}
enter code here
If you're doing this as an exercise (just using a ready made logger isn't an option) you could try a producer / consumer system.
Either make an Init function for your logger, or use the static constructor - inside it, launch a new System.Threading.Thread, which just runs through a while(true) loop.
Create a new Queue<string> and have your logging function enqueue onto it.
Your while(true) loop looks for items on the queue, dequeues them, and logs them.
Make sure you lock your queue before doing anything with it on either thread.
sry, but you may not reinvent the wheel:
choose log4net (or any other (enterprise) logging-engine) as your logger!
Ok, simply put you need to create a ThreadSafe static class. Below are some code snippets, a delegate that you call from any thread, this points to the correct thread, which then invokes the WriteToFile function.
When you start the application that you want to log against, pass it the following, where LogFile is the filename and path of your log file.
Log.OnNewLogEntry += Log.WriteToFile (LogFile, Program.AppName);
Then you want to put this inside your static Logging class. The wizard bit is the ThreadSafeAddEntry function, this will make sure you are in the correct Thread for writing the line of code away.
public delegate void AddEntryDelegate(string entry, bool error);
public static Form mainwin;
public static event AddEntryDelegate OnNewLogEntry;
public static void AddEntry(string entry) {
ThreadSafeAddEntry( entry, false );
}
private static void ThreadSafeAddEntry (string entry, bool error)
{
try
{
if (mainwin != null && mainwin.InvokeRequired) // we are in a different thread to the main window
mainwin.Invoke (new AddEntryDelegate (ThreadSafeAddEntry), new object [] { entry, error }); // call self from main thread
else
OnNewLogEntry (entry, error);
}
catch { }
}
public static AddEntryDelegate WriteToFile(string filename, string appName) {
//Do your WriteToFile work here
}
}
And finally to write a line...
Log.AddEntry ("Hello World!");
What you have in this case is a typical producer consumer scenario - many threads produce log entries and one thread writes them out to a file. The MSDN has an article with sample code for this scenario.
For starters, your logging mechanism should generally avoid throwing exceptions. Frequently logging mechanisms are where errors get written to, so things get ugly when they also start erroring.
I would look into the BackgroundWorker class, as it allows you to fork off threads that can do the logging for you. That way your app isn't slowed down, and any exceptions raised are simply ignored.