473,386 Members | 1,812 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,386 software developers and data experts.

c# multithreading -- really confused!

9
Hi all,

I have several threads running (basically they are copying files from one place to another). I am using log4net for logging...However, sometimes i get a log4net error due to log file being used by other process. I thought about creating a method which will take care of the logging...all threads will used a delegate to call the method that does the logging

static readonly object lockerLog = new object();
public delegate void UpdateLogDelegate(string txt,string type)

Expand|Select|Wrap|Line Numbers
  1. public void LogInfo(string txt, string type)
  2.         {
  3.             lock (lockerLog)
  4.             {
  5.                 switch (type)
  6.                 {
  7.                     case "ERROR":
  8.                     Logging.log.Error(txt);
  9.                     break;
  10.  
  11.                     case "DEBUG":
  12.                     Logging.log.Debug(txt);
  13.                     break;
  14.                 }
  15.  
  16.             }
  17.  
  18.         }
  19.  
My question is: as the code stands right now, I thread might have acquired the lock on the call to the logging, but a second thread might have come along and modified the method parameters "txt" and "type", right? Is there a way of locking the entire method?

I think that another option could be to create a class to hold the txt and type and pass the object to the method
Expand|Select|Wrap|Line Numbers
  1. public void LogInfo(LogText lt)
  2.         {
  3.             lock (lt)
  4.             {
  5.                 switch (lt.type)
  6.                 {
  7.                     case "ERROR":
  8.                     Logging.log.Error(lt.txt);
  9.                     break;
  10.  
  11.                     case "DEBUG":
  12.                     Logging.log.Debug(lt.txt);
  13.                     break;
  14.                 }
  15.             }
  16.  
  17.         }
  18.  
Or just locking the logger
Expand|Select|Wrap|Line Numbers
  1. class Logging
  2. {
  3. public static readonly ILog log = LogManager.GetLogger(typeof(Logging));
  4. Logging() {}
  5. public string Initialize()
  6. {
  7. .....
  8. }
  9. }
  10.  
thus when I called it from another class I do:
Expand|Select|Wrap|Line Numbers
  1. lock(Logging.log)
  2. {
  3. Logging.log.Info("hopefully there is a lock in the log right now ...");
  4. }
  5.  
Anyone knows what will be the proper way of doing this, if ANY of the ones I have mentioned? I am new to threading and feel a bit confused

Cheers
Feb 27 '09 #1
9 10304
tlhintoq
3,525 Expert 2GB
I've looked at, but not played with Log4Net. Instead I just do my own logging. In my function I raise an event with the log text. My logger is subscribed to the event. Thus it is listening for the next item to log, not being commanded to log *now*. Could something similar work for you? Raise a custom event with the logging data so that a single method then funnels the events to Log4Net?
Feb 27 '09 #2
mldisibio
190 Expert 100+
At the heart of the problem, no matter what logging API you use, is the fact that a single text stream is being accessed by more than one thread.
Locking is a good start, but remember
Expand|Select|Wrap|Line Numbers
  1. lock(someLocker){do logging}
means that any entering the "do logging" block will only proceed when the previous block is completely finished. Effectively, you will block logging threads one at a time, undermining the purpose of multi-threading.

There could be several solutions, including creating an "asynchronous" Appender in log4net, but at a high level I would recommend what tlhintoq said with one addition:

If you set up an event that your logging object can listen to, your classes will fire a "LogTextIsReady" type of event and then carry on their task. The logging listener alone then becomes responsible for maintaining the synchronous access to the textstream when asynchronous writes are requested.
(Before I go on, let me mention that you will want to try to make the "LogTextIsReady" event as globally accessible as possible so that you are not repeating the same event in all your individual task classes. Use an interface or perhaps a static class or singleton object.)

At a basic design, your logging listener really just needs a thread signal, such as an System.Threading.AutoResetEvent. This is called a EventWaitHandle and allows an operation to proceed only when the previous thread or block of code finishes and tells the signal that the next block or thread may proceed.

In the case of a logger, each write method will call "WaitOne()" - meaning wait until the previous write is finished. When it finishes writing, it calls "Set()" to let the signal know to allow the next thread to start.
So your logger (which can still be a log4net logger) will be queuing up texts to write to the stream, one at a time, but because your logger is event driven, the classes and threads which called the listener do not need to wait for the event handler to complete.

By following this model, you will probably then only have one logger instance, which means you lose the log4net "named logger" and implicit tracing feature. If that is important to your application, as mentioned you can try encapsulating the above thread control into a custom Appender or Forwarder.
Feb 28 '09 #3
inxanu
9
Many thanks for your input...It has been really helpfull. I will have to think about changing the logging. But one thing that intrigues me:

At present I am locking the logger

Expand|Select|Wrap|Line Numbers
  1. lock(Logging.log)
yet i still got this error:

log4net:ERROR [FileAppender] Unable to acquire lock on file d:\logfiles\
WSFileCopier.log. The process cannot access the file 'd:\logfiles\WSFileCopier.log' because it is being used by another process.

This is my log4net appender in the config file:
Expand|Select|Wrap|Line Numbers
  1.     <appender name="RollingFileAppender" type="log4net.Appender.FileAppender">
  2.       <file value="d:\logfiles\WSFileCopier.log" />
  3.       <appendToFile value="true" />
  4.       <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
  5.       <layout type="log4net.Layout.PatternLayout">
  6.         <conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
  7.       </layout>
  8.     </appender>
  9.  
Also my log4net appender has been configured using the minimal locking which allows multiple processes to write to the same file (eventhough at this point I am only using one...) so, why am I getting this?
Mar 2 '09 #4
mldisibio
190 Expert 100+
Not sure why. You might try removing your lock altogether just to see if it is your own lock that is causing the access denial. Does this error happen before even the first write, or is something being written to file, and this happens when a second process attempts to write?

Don't forget, locking just says: "you can't have access to this object until the lock is released." That does not mean the caller will wait for it to be released...you need the WaitHandle mechanism to actually make a thread wait for a lock to be released. So while locking the logger makes sure that no two processes are accessing the stream at the same time, it does not force a thread to wait. Therefore, as soon as the second call attempts to access the stream, it is locked out and could easily return immediately with a message "cannot acquire lock."

Of course, it could also be something silly, like another text editor or application which has an exclusive lock on your log file...this is unlikely but it happens sometimes.

Finally, some authors will recommend...although it is not a hard and fast rule...that you not lock on your object doing the work, but rather on a separate object whose sole purpose is to server as a lock. So instead of
Expand|Select|Wrap|Line Numbers
  1. lock(Logging.log){...}
do (at global or static level)
Expand|Select|Wrap|Line Numbers
  1. object loggingLocker = new object();
  2.  
and in any code block:
Expand|Select|Wrap|Line Numbers
  1. lock(loggingLocker){log.WriteInfo()...etc}
Mar 2 '09 #5
inxanu
9
"Does this error happen before even the first write, ..." --> No
"or is something being written to file, and this happens when a second process attempts to write?" --> No, I do not have any more processes writting to this file, or at least I shouldn't. I am using the lock()... I will create a global lock as you suggest.

Also
"Don't forget, locking just says: "you can't have access to this object until the lock is released."

I have to create up to 100 threads to do different jobs. Once a thread finishes its job, it updates their status. The thread status info is being held in an ArrayList called jobsToDo. At present I was just using a lock (without any signalling) thinking that the lock already synchronized the threads

Expand|Select|Wrap|Line Numbers
  1. public void UpdateJobList(int jobid)
  2.         {
  3.            //Update jobList
  4.             lock (jobsToDo)
  5.             {
  6.                 bool bFound = false;
  7.                 //Find the job on the list and update it
  8.                 foreach (Jobs j in jobsToDo)
  9.                 {
  10.                     if (j.JobId == jobid)
  11.                     {
  12.                         bFound = true;
  13.                         j.ThreadId = -1;
  14.                         j.Status = "Finish";
  15.                         Logging.log.Debug("Job id \"" + j.JobId + "\" status has been updated to \"Finish\"");
  16.             Thread.Sleep(5000); //just to be sure that more than one thread will reach this method while
  17.                         //one thread in in here doing its job.
  18.                         break;
  19.                     }
  20.                 }
  21.                 if (!bFound)
  22.                 {
  23.                     Logging.log.Error("Could not found job id \"" + jobid + "\" in job list");
  24.                 }
  25.             }
  26.         }
  27.  
  28.  
Now, I am checking the log and everythings seems to be fine. How can that be!? Please note how thread Job 2, Job 3, Job 4 arrive to the lock statement, and they wait for it (or at least they seem to do that) untill Job 1 releases the lock, then Job 2 goes in and updates it is status, then after 5 sec, Job 3 goes in ...and so on...this is the expected behaviour I want...However, it seems that a lock by itself should not produced this output right? HELP! please, I really need to understand why this is happening...

Log:
Expand|Select|Wrap|Line Numbers
  1. 2009-03-02 18:53:04,498 [Job 1] DEBUG ClassLibrary1.Logging - Going to update job list for job id "1"
  2. 2009-03-02 18:53:04,498 [Job 1] DEBUG ClassLibrary1.Logging - Job id "1" status has been updated to "Finish"
  3. 2009-03-02 18:53:05,483 [9] DEBUG ClassLibrary1.Logging - Job Id 2 has been started
  4. 2009-03-02 18:53:05,498 [Job 2] DEBUG ClassLibrary1.Logging - Going to update job list for job id "2"
  5. 2009-03-02 18:53:06,498 [9] DEBUG ClassLibrary1.Logging - Job Id 3 has been started
  6. 2009-03-02 18:53:06,498 [Job 3] DEBUG ClassLibrary1.Logging - Going to update job list for job id "3"
  7. 2009-03-02 18:53:07,498 [9] DEBUG ClassLibrary1.Logging - Job Id 4 has been started
  8. 2009-03-02 18:53:07,498 [Job 4] DEBUG ClassLibrary1.Logging - Going to update job list for job id "4"
  9. 2009-03-02 18:53:08,514 [9] DEBUG ClassLibrary1.Logging - Job Id 5 has been started
  10. 2009-03-02 18:53:08,514 [Job 5] DEBUG ClassLibrary1.Logging - Going to update job list for job id "5"
  11. 2009-03-02 18:53:09,498 [Job 2] DEBUG ClassLibrary1.Logging - Job id "2" status has been updated to "Finish"
  12. 2009-03-02 18:53:09,530 [9] DEBUG ClassLibrary1.Logging - Job Id 6 has been started
  13. 2009-03-02 18:53:09,530 [Job 6] DEBUG ClassLibrary1.Logging - Going to update job list for job id "6"
  14. 2009-03-02 18:53:10,530 [9] DEBUG ClassLibrary1.Logging - Job Id 7 has been started
  15. 2009-03-02 18:53:10,530 [Job 7] DEBUG ClassLibrary1.Logging - Going to update job list for job id "7"
  16. 2009-03-02 18:53:11,530 [9] DEBUG ClassLibrary1.Logging - Job Id 8 has been started
  17. 2009-03-02 18:53:11,545 [Job 8] DEBUG ClassLibrary1.Logging - Going to update job list for job id "8"
  18. 2009-03-02 18:53:12,530 [9] DEBUG ClassLibrary1.Logging - Job Id 9 has been started
  19. 2009-03-02 18:53:12,545 [Job 9] DEBUG ClassLibrary1.Logging - Going to update job list for job id "9"
  20. 2009-03-02 18:53:13,545 [9] DEBUG ClassLibrary1.Logging - Job Id 10 has been started
  21. 2009-03-02 18:53:13,561 [Job 10] DEBUG ClassLibrary1.Logging - Going to update job list for job id "10"
  22. 2009-03-02 18:53:14,498 [Job 3] DEBUG ClassLibrary1.Logging - Job id "3" status has been updated to "Finish"
  23.  
Mar 2 '09 #6
mldisibio
190 Expert 100+
I think you are good. Let me explain why I think this works. Mind you, I am not an absolute expert or authority on the subject.

In your previous example, you were locking on the logger itself (Logging.log). Since this object owns the access to the log file, a lock on it basically locks out the file as well, no matter how access rights are configured initially.
So probably your second and third job come along, attempt to get a handle to Logging.log or its underlying file, and are told "sorry, I'm locked" so they return with "cannot access file."

In your more recent example, you are locking on the ArrayList (which by the way has some inherent thread safety built in). The logger itself is not locked. So Job 2 and Job 3 come and are told: "Wait, you can enter this method "UpdateJobList" as soon as the ArrayList is available." By the time they are allowed to call on the logger itself, the file has released its previous write lock.

You have the simplest locking model and it seems to work fine. I would leave it alone. Signalling would add some robustness...for example, if your application needed to terminate, you might be able to check if some log writes were still pending before terminating completely...but you can add those enhancements later.

For a great reference to threading and locking, I highly recommend:
http://www.albahari.com/threading/ [Threading in C# by Albahari]
Mar 2 '09 #7
mldisibio
190 Expert 100+
@mldisibio
You proved that what I said is not entirely correct. How about if I just quote an expert...(source Albahari - same link as above):

The lock statement (aka Monitor.Enter / Monitor.Exit) is one example of a thread synchronization construct. While lock is suitable for enforcing exclusive access to a particular resource or section of code, there are some synchronization tasks for which it's clumsy or inadequate, such as signaling a waiting worker thread to begin a task.

The Win32 API has a richer set of synchronization constructs, and these are exposed in the .NET framework via the EventWaitHandle, Mutex and Semaphore classes. Some are more useful than others: the Mutex class, for instance, mostly doubles up on what's provided by lock, while EventWaitHandle provides unique signaling functionality.
Mar 2 '09 #8
inxanu
9
Hi mldisibio,

I did in fact read through the information you suggest (Albahari) thats why I thought that by locking the arraylist i was taking care of the thread synchronization. Why was I still unsure?
1) It is the first time I am using multithreading and ...
2) English is not my first language, thus sometimes I can misinterpret information (which now has been totally clarify, a big THANK YOU!)
3) As with the vast majority of information on internet, you are never sure the level of accuracy

Anyway, I REALLY APPRECIATE YOUR HELP :)

Now, my logging...I am going to try using a global lock to see how it goes...if i still get the "random" (I say random because I cannot pint point when it happens, also I cannot recret it) I will have to implement the logging as you suggested. I tried removing the lock on the logging but I was still getting the error.

Also you said
[quote]
you were locking on the logger itself (Logging.log). Since this object owns the access to the log file, a lock on it basically locks out the file as well, no matter how access rights are configured initially.
[quote]
If we go to the basics one moment, basically, whenever there is a static field which is being access by more than one thread, a lock should be used, right?Then,my logger log is being accessed by more than one thread, shouldnt i use a lock on it? Isnt' the fileappender the one that it is lock, and thread-safe?Isnt the fileappender something totally different than my logger?My understanding was that the minimal lock file appender allows, for instance, two different applications, each with it is on logger, to write to the same log file safely...please correct me if I am wrong

Cheers
Mar 2 '09 #9
mldisibio
190 Expert 100+
Couple of things:
1. The Log4Net API explicitly says that the FileAppender is NOT safe for multithreaded applications (which just means it does not come out of the box with thread safety...you can still build thread safety around it, as you are doing).

2. The MinimalLock configuration basically says: "for any write operation, acquire the lock to the underlying stream, write, release the lock." This means that two threads could write to the same file during the same overall job - but not at the same time! Writes still happen one after the other. But in an ExclusiveLock, the first thread would actually have to call CloseFile() before another thread could write to the same file. (Its real purpose is to allow one of those threads to move, copy or even delete the file without stopping logging entirely.)

3. Just a clarification: when you lock a primitive value, such as an int or a boolean, you are telling the runtime: "do not allow any caller to change this value until I release the lock on it." So a very simple example:
Expand|Select|Wrap|Line Numbers
  1. static int MyInt;
  2. static void SaveWorldIfTwo(){
  3. MyInt = 2;
  4. if((2/MyInt) == 1) SaveWorld();
  5. }
If MyInt is accessed by multiple threads, then without locking, another thread can change MyInt to zero between the call "MyInt = 2" and "2/MyInt" leading to a "division by zero" exception and the world not being saved.

However, when you lock on an object or you lock a block of code, then the focus is not on preserving a primitive value, but rather on not letting other code have access to that object or block of code until the lock is released. In some cases, you might even cause a thread to hang because you have locked an object (such as a logger) and perhaps in the case of third party libraries or in an event driven model, other classes need to access it before it exits your lock, but they cannot.

4. Finally, in terms of the relationship of the log4net ILog, Appender and such, those are great questions I am not qualified to answer. The API makes them appear to be completely separate objects, but it is quite possible they are each abstractions which have underlying dependencies. And in the case of classes which access file streams, it is very likely that the stream locking is shared among the different API layers.
Mar 3 '09 #10

Sign in to post your reply or Sign up for a free account.

Similar topics

10
by: gianguz | last post by:
The question is about the possible use of inlining to improve performance in a heavy multithreading environment (200+ threads). If we have to work with applications in which threads aren't I/O...
16
by: Robert Zurer | last post by:
Can anyone suggest the best book or part of a book on this subject. I'm looking for an in-depth treatment with examples in C# TIA Robert Zurer robert@zurer.com
7
by: Shapiro | last post by:
I have a scenario where I log a resquest to a database table and update the request with a corresponding response including the response time. I am using an HttpModule to do this. My challenge...
2
by: SStory | last post by:
Here is the situation. I want to display Icons, Type of file etc from a file extension. Upon initial program load I may only need icons for certain files. But other operations will require...
55
by: Sam | last post by:
Hi, I have a serious issue using multithreading. A sample application showing my issue can be downloaded here: http://graphicsxp.free.fr/WindowsApplication11.zip The problem is that I need to...
5
by: sandy82 | last post by:
Whats actuallly multithreading is ... and how threading and multithreading differ . Can any1 guide how multithreading is used on the Web .. i mean a practical scenario in which u use...
0
by: denis.cornehl | last post by:
Hi, I have an unusual Problem with DB2. It is DB2 Version 7 and Fixpack 13 under Windows. We have written an application server which is accessing db2 via c++ and the cli interface. We used...
2
by: Pradnya Patil | last post by:
hi , I am trying to draw ' html div-tag ' on the screen which will resemble a rectangle through vb.net code. I want it to be drawn faster...so I introduced multithreading using Threadpool. I...
2
by: lizii | last post by:
At the moment i am trying to write into a richtextbox using the Rtf call from another richtextbox. However realising that richtextbox do not have multithreading - i know i need to invoke this...
0
by: taylorcarr | last post by:
A Canon printer is a smart device known for being advanced, efficient, and reliable. It is designed for home, office, and hybrid workspace use and can also be used for a variety of purposes. However,...
0
by: aa123db | last post by:
Variable and constants Use var or let for variables and const fror constants. Var foo ='bar'; Let foo ='bar';const baz ='bar'; Functions function $name$ ($parameters$) { } ...
0
by: ryjfgjl | last post by:
In our work, we often receive Excel tables with data in the same format. If we want to analyze these data, it can be difficult to analyze them because the data is spread across multiple Excel files...
0
by: emmanuelkatto | last post by:
Hi All, I am Emmanuel katto from Uganda. I want to ask what challenges you've faced while migrating a website to cloud. Please let me know. Thanks! Emmanuel
1
by: nemocccc | last post by:
hello, everyone, I want to develop a software for my android phone for daily needs, any suggestions?
1
by: Sonnysonu | last post by:
This is the data of csv file 1 2 3 1 2 3 1 2 3 1 2 3 2 3 2 3 3 the lengths should be different i have to store the data by column-wise with in the specific length. suppose the i have to...
0
by: Hystou | last post by:
There are some requirements for setting up RAID: 1. The motherboard and BIOS support RAID configuration. 2. The motherboard has 2 or more available SATA protocol SSD/HDD slots (including MSATA, M.2...
0
Oralloy
by: Oralloy | last post by:
Hello folks, I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>". The problem is that using the GNU compilers,...
0
jinu1996
by: jinu1996 | last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.