Static Constructor Called Twice

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • =?Utf-8?B?TWF0dA==?=

    Static Constructor Called Twice

    I'm having a problem with a static class constructor being called twice. I
    have the static class MasterTaskList which uses a BackgroundWorke r to execute
    multiple methods on a separate thread. The static constructor calls a reset
    function which creates a new instance of BackgroundWorke r and attaches the
    appropriate event handlers. There is also a static method ReportProgress for
    the called methods to do just that.

    What is happening is that the static class is initialized the first time,
    functions are added to the list to be executed, the BackgroundWorke r is
    started, and one of the methods called by the BackgroundWorke r's thread calls
    MasterTaskList. ReportProgress( ). This access of the MasterTaskList class
    causes the static constructor to be called again. See below for the callstack
    from the second call to the static constructor.

    What I can't fathom is how it is possible for a static method from a static
    class to appear in the callstack for a static constructor.

    Here's a couple of other twists:

    1) This only happens when the calls are made by a Windows service. Identical
    calls in a regular Windows app work fine.

    2) The call to ReportProgress( ) that does this isn't the first time it's
    called within the scope of
    Master.Common.T ask.MasterTaskL ist.InternalCal culation(). It is the first time
    it's called within Master.Bar.Calc ulation.Foo.Foo Calculation.Loa dData()

    3) The static constructor sets the BackgroundWorke r instance of
    MasterTaskList to a new BackgroundWorke r. The original BackgroundWorke r
    continues to run, but doesn't fire it's RunWorkerComple ted event when
    finished.

    4) I can attach to this process running as a Windows service and step
    through to right before it calls the static constuctor again. Using
    Quickwatch, I can see that the MasterTaskList it's about to call is already
    initialized. After it's called, the events in the class all get disconnected
    and the only thing left running is the stranded BackgroundWorke r thread.

    This one has left me scratching my head.

    Thanks for any help in advance,
    Matt


    Here is the call stack from the static constructor:

    at Master.Common.T ask.MasterTaskL ist..cctor()
    at Master.Common.T ask.MasterTaskL ist.ReportProgr ess()
    at Master.Bar.Calc ulation.Foo.Foo Calculation.Loa dData(Object sender,
    EventArgs e)
    at Master.Bar.BarM aster.LoadData( Object sender, EventArgs e)
    at Master.Common.T ask.MasterTaskL ist.ExecuteTask (MasterTask currentTask)
    at Master.Common.T ask.MasterTaskL ist.InternalCal culation(Object sender,
    DoWorkEventArgs e)
    at System.Componen tModel.Backgrou ndWorker.OnDoWo rk(DoWorkEventA rgs e)
    at System.Componen tModel.Backgrou ndWorker.Worker ThreadStart(Obj ect
    argument)
    at
    System.Runtime. Remoting.Messag ing.StackBuilde rSink._PrivateP rocessMessage(I ntPtr
    md, Object[] args, Object server, Int32 methodPtr, Boolean fExecuteInConte xt,
    Object[]& outArgs)
    at
    System.Runtime. Remoting.Messag ing.StackBuilde rSink.PrivatePr ocessMessage(Ru ntimeMethodHand le
    md, Object[] args, Object server, Int32 methodPtr, Boolean fExecuteInConte xt,
    Object[]& outArgs)
    at
    System.Runtime. Remoting.Messag ing.StackBuilde rSink.AsyncProc essMessage(IMes sage msg, IMessageSink replySink)
    at
    System.Runtime. Remoting.Proxie s.AgileAsyncWor kerItem.ThreadP oolCallBack(Obj ect o)
    at System.Threadin g._ThreadPoolWa itCallback.Wait Callback_Contex t(Object
    state)
    at System.Threadin g.ExecutionCont ext.Run(Executi onContext
    executionContex t, ContextCallback callback, Object state)
    at System.Threadin g._ThreadPoolWa itCallback.Perf ormWaitCallback (Object
    state)

  • Jon Skeet [C# MVP]

    #2
    Re: Static Constructor Called Twice

    Matt <inventorydynam ics@nospam.nosp amwrote:
    I'm having a problem with a static class constructor being called twice. I
    have the static class MasterTaskList which uses a BackgroundWorke r to execute
    multiple methods on a separate thread. The static constructor calls a reset
    function which creates a new instance of BackgroundWorke r and attaches the
    appropriate event handlers. There is also a static method ReportProgress for
    the called methods to do just that.
    This does indeed look a bit odd. A few questions:

    1) Is it definitely a static constructor as opposed to a type
    initializer created by static variable initializers in your static
    class?

    2) Are multiple AppDomains involved at all? Don't forget that each
    static constructor is called once per type *per AppDomain*. If somehow
    MasterTaskList. ReportProgress from AppDomain A is using MasterTaskList
    from AppDomain B, that would sort of explain things.

    3) Can you reproduce it in a short but complete program?

    See http://www.pobox.com/~skeet/csharp/complete.html for details of
    what I mean by that. I know that being a Windows Service will make this
    harder, but it would really be useful for debugging...

    --
    Jon Skeet - <skeet@pobox.co m>
    http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet
    If replying to the group, please do not mail me too

    Comment

    • =?Utf-8?B?TWF0dA==?=

      #3
      Re: Static Constructor Called Twice

      1) Nope, it's the following:

      static MasterTaskList( )
      {
      Trace.WriteLine ("Static Constructor");
      Trace.WriteLine (AppDomain.Curr entDomain.Frien dlyName);
      Trace.WriteLine (new StackTrace());
      ResetWorker();
      }

      And I can tell by the field values before and after the call that the second
      call does initialize the static fields as well. If it didn't, I could just
      call ResetWorker manually and not care how many times it calls the static
      constructor. It's the wiping of my events that's a problem.

      2) As you can see from the code above, I thought of that too, and nope, it's
      all still in the AppDomain of the service.

      3) I'll start working on the sample, but it looks like it's not going to be
      very short. I have a windows service which references an assembly that loads
      other assemblies using reflection and then adds methods in those assemblies
      to the list that the BackgroundWorke r executes. At the least that's 4 dlls
      and a service. I'll see if I can get it to do the same without all of that
      structure, but it might be something about that structure that causes this.

      Thanks for the quick reply,
      Matt


      Comment

      • Jon Skeet [C# MVP]

        #4
        Re: Static Constructor Called Twice

        Matt <inventorydynam ics@nospam.nosp amwrote:
        1) Nope, it's the following:
        >
        static MasterTaskList( )
        {
        Trace.WriteLine ("Static Constructor");
        Trace.WriteLine (AppDomain.Curr entDomain.Frien dlyName);
        Trace.WriteLine (new StackTrace());
        ResetWorker();
        }
        >
        And I can tell by the field values before and after the call that the second
        call does initialize the static fields as well. If it didn't, I could just
        call ResetWorker manually and not care how many times it calls the static
        constructor. It's the wiping of my events that's a problem.
        Right. As a short-term hacky fix, you could always keep a boolean value
        to keep track of whether or not it's already been called, and just
        return quickly if this isn't the first call.
        2) As you can see from the code above, I thought of that too, and nope, it's
        all still in the AppDomain of the service.
        Hmm. Very puzzling.
        3) I'll start working on the sample, but it looks like it's not going to be
        very short. I have a windows service which references an assembly that loads
        other assemblies using reflection and then adds methods in those assemblies
        to the list that the BackgroundWorke r executes. At the least that's 4 dlls
        and a service. I'll see if I can get it to do the same without all of that
        structure, but it might be something about that structure that causes
        this.
        It's probably worth taking a backup of the "real" one and gradually
        hacking stuff out. That way you'll either end up finding out which bit
        causes the problem, or getting down to a much smaller sample app.

        If it ends up being too big to post, you could always mail it to me or
        put it on a download site.

        --
        Jon Skeet - <skeet@pobox.co m>
        http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet
        If replying to the group, please do not mail me too

        Comment

        • =?Utf-8?B?TWF0dA==?=

          #5
          Re: Static Constructor Called Twice

          I got the test program to call the static constructor twice. What made it do
          it was changing Environment.Cur rentDirectory so that it's running in a
          different directory than the WindowsService. exe. It's still in the same
          AppDomain and the directory doesn't change after the first call to the
          constructor, but it still calls it twice.

          I'll trim back the sample code and post it soon.


          Comment

          • Walter Wang [MSFT]

            #6
            Re: Static Constructor Called Twice

            Hi Matt,

            If you will send the code via email, please also send a copy to me. Thanks.


            Regards,
            Walter Wang (wawang@online. microsoft.com, remove 'online.')
            Microsoft Online Community Support

            =============== =============== =============== =====
            When responding to posts, please "Reply to Group" via your newsreader so
            that others may learn and benefit from your issue.
            =============== =============== =============== =====

            This posting is provided "AS IS" with no warranties, and confers no rights.

            Comment

            • Jon Skeet [C# MVP]

              #7
              Re: Static Constructor Called Twice

              On Aug 8, 12:58 am, Matt <inventorydynam ...@nospam.nosp amwrote:
              I got the test program to call the static constructor twice. What made it do
              it was changing Environment.Cur rentDirectory so that it's running in a
              different directory than the WindowsService. exe. It's still in the same
              AppDomain and the directory doesn't change after the first call to the
              constructor, but it still calls it twice.
              >
              I'll trim back the sample code and post it soon.
              I've managed to reproduce it in a console application. The code is at
              the bottom of this post. In short, it consists of two assemblies:
              Plugin.dll and Test.exe.
              Plugin.dll reference Test.exe, but not the other way round.
              Test.exe contains a Helper class with a static constructor.
              Test.exe creates a fresh directory, and takes a list of assembly files
              from the command line.
              One at a time, it performs the following steps:
              1) Copy the file into the directory
              2) Load the assembly using Assembly.LoadFr om
              3) Try to find a type called "Plugin"
              4) If the type exists, create an instance and call its Plug method
              with reflection

              The Plugin type in Plugin.dll calls a static method in Helper.

              The results are that if you run:
              test test.exe plugin.dll
              you see the Helper static constructor twice. If you run
              test plugin.dll test.exe
              then you only see the Helper static constructor once.

              In the first case, when the JIT tries to resolve Helper for Plugin, it
              finds that there are two copies loaded. It uses the second copy, I
              *believe* that's because it notices that the second copy was loaded
              from the same directory as Plugin itself.

              In the second case, when the JIT tries to resolve Helper for Plugin,
              only one copy of Helper has been loaded at that point, so the existing
              type is reused, and the static constructor isn't called again.

              Basically, anything using Assembly.LoadFr om needs to be pretty
              careful :)

              Here's the code:

              ----------------- Test.cs ---------------------
              using System;
              using System.IO;
              using System.Collecti ons.Generic;
              using System.Reflecti on;

              public class Helper
              {
              static Helper()
              {
              Console.WriteLi ne ("Helper static constructor");
              }

              public static void Print(string x)
              {
              Console.WriteLi ne (x);
              }
              }

              public class Program
              {
              const string PluginDirectory = "Plugins";

              static void Main(string[] args)
              {
              Helper.Print("S tarting");
              if (Directory.Exis ts(PluginDirect ory))
              {
              Directory.Delet e(PluginDirecto ry, true);
              }
              Directory.Creat eDirectory(Plug inDirectory);

              foreach (string x in args)
              {
              string target = Path.Combine(Pl uginDirectory, x);
              File.Copy(x, target);
              Assembly ass = Assembly.LoadFr om(target);
              Type pluginType = ass.GetType("Pl ugin");
              if (pluginType != null)
              {
              object plugin = Activator.Creat eInstance(plugi nType);
              pluginType.GetM ethod("Plug").I nvoke(plugin, null);
              }
              }
              }
              }

              ----------------------- Plugin.cs ---------------------
              public class Plugin
              {
              public void Plug()
              {
              Helper.Print("H ello");
              }
              }


              Hope that helps,
              Jon

              Comment

              Working...