tags:

views:

95

answers:

2

hi, i am using following class to provide access to language resources in an asp.net application. i render the page for selected language by getting the text values from database. so i try to optimize fetching texts by caching them in a static datatable. However, i am not sure whether its always safe to read from tableResources that it may be re-created by UpdateResources function. I know GC will not release the object when its read by Rows.Find but i don't know much about GC. It may cause a deadlock or stuck GC, whatever else. (I think IL instructions are not atomic, unless the ones that compiled to a single CPU instruction). Please, help me to comprehend this.

public class Resources
{
    public static DataTable tableResources;
    public static object objSync = new object();
    private PageLangs PageLang;

    static Resources()
    {
        UpdateResources();
    }

    public Resources(PageLangs pageLang)
    {
        PageLang = pageLang;
    }

    public static void UpdateResources()
    {
        OleDbConnection con = ProjectLib.CreateDBConnection();
        try
        {
            con.Open();

            OleDbDataAdapter adap = new OleDbDataAdapter("SELECT Resource0,Resource1,Resource2,Resource3,Resource4,Resource5,Resource6,ResourceCode FROM Resources", con);
            DataTable dt = new DataTable();
            adap.Fill(dt);
            adap.Dispose();
            dt.PrimaryKey = new DataColumn[] { dt.Columns["ResourceCode"] };
            // DataTable is thread-safe for multiple reads but not for writes so sync. it.
            lock (objSync)
            {
                tableResources = dt;
            }
        }
        catch
        {
        }
        finally
        {
            ProjectLib.CloseDBConnection(con);
        }
    }

    public string this[string resourceCode]
    {
        get
        {
            try
            {
                DataRow row = tableResources.Rows.Find(resourceCode);

                if (row != null)
                    return row[(int)PageLang] as string;
                else
                    return resourceCode;
            }
            catch
            {
                return null;
            }
        }
    }
}
A: 

I think you want to achieve that the callers of the index property aren't disturbed by a parallel call to the UpdateResources-function from another thread.

Your single lock-statement has no effect at all. When you want to synchronize the access of the tableResources member - you have to do synchronize all places where its being accessed (UpdateResources and index property).

When you don't synchronize there might be a raise condition, because when you call UpdateResources, there is no reference to the old DataSet - even when tableResources.Rows.Find(resourceCode) is executing at that time.

In addition you should modify the access modifier of tableResources to private or protected.

Concerning performance, you might want to implement a more complex synchronization mechanism which fits better to the multiple-readers-single-writer pattern. See these links:

  1. Another SO Question
  2. Wikipedia article
Jan
but its already being accessed by tableResources.Rows.Find expression why would not there be a reference to it.
marksxer
You need a reference from outside the object to prevent garbage collection from disposing your object.In your posted code, you just have one single reference via the tableResources variable.Why aren't you just syncing the reads also? Have you concerns about performance?
Jan
but indexer method will hold tableResources reference until it exits. so tableResources will be safe when indexer method is executing and its not a concern if its removed and GCd after indexer exits.UpdateResources method will be called rarely so its not a much concern.i don't want to sync reads because of performance. it will be called in asp.net pages multiple times per request.
marksxer
no, the indexer doesn't hold a reference to the object. it just uses the possibly changing reference tableResources. See my edit on my answer for performance concerns.
Jan
so, you mean that it just *uses* but does not hold a reference so i have to assign it to a local variable. DataTable dt = tableResources; do you mean this?
marksxer
Right, when you assign a local variable, the indexer has its own reference to the "old" DataTable. When UpdateResources drops its reference in that moment, you should be safe.
Jan
so, when some of tableResources' method is invoked, its reference is required in that method, so that method will preserve it, am i right? and as a last point, is the assignment DataTable dt = tableResources; atomic in .net? or a thread may change the half of tableResources value while another one is performing this assignment and we have left with a inconsistent reference?
marksxer
The assignment is atomic, there is no concept like a "half" or "broken" reference. But when you don't synchronize the assignment, you might get the old or the new dataTable. But that shouldn't be a problem.
Jan
i have made some tests and observed that Rows is not disposed so tableResources object even when Find() is executing because accessing Rows property prevents GC to collect tableResources. so, holding a local reference to tableResources is not required and indexer method is completely thread-safe.
marksxer
even lock block is not required in UpdateResources() for thread-safety as reference read/writes are atomic.
marksxer
How can you test that? The GC is non deterministic. Objects won't be releases immediately after the last reference is gone away.
Jan
+1  A: 

I have used the following class instead of DataTable. After setting new instance of object, i have forced garbage collection by

GC.Collect(); 
GC.WaitForPendingFinalizers();

If AMethod() is called then destructor of DataTableX is not called but if its not then destructor is called.

I have tested even while objDataTableX.AMethod() is about to be called. I have stepped into the disassembly code and freeze debugger when objDataTableX reference is fetched by a single instruction and then continue debugging with another thread that changes the reference. Reference is changed but previous DataTableX reference is not disposed so i thaw the other thread and it executes well with the previous reference.

public class DataTableX
{
    public void AMethod()
    {
    }

    ~DataTableX()
    {

    }
}
marksxer