Static Constructor Called Twice


G

Guest

I'm having a problem with a static class constructor being called twice. I
have the static class MasterTaskList which uses a BackgroundWorker to execute
multiple methods on a separate thread. The static constructor calls a reset
function which creates a new instance of BackgroundWorker 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 BackgroundWorker is
started, and one of the methods called by the BackgroundWorker'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.Task.MasterTaskList.InternalCalculation(). It is the first time
it's called within Master.Bar.Calculation.Foo.FooCalculation.LoadData()

3) The static constructor sets the BackgroundWorker instance of
MasterTaskList to a new BackgroundWorker. The original BackgroundWorker
continues to run, but doesn't fire it's RunWorkerCompleted 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 BackgroundWorker 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.Task.MasterTaskList..cctor()
at Master.Common.Task.MasterTaskList.ReportProgress()
at Master.Bar.Calculation.Foo.FooCalculation.LoadData(Object sender,
EventArgs e)
at Master.Bar.BarMaster.LoadData(Object sender, EventArgs e)
at Master.Common.Task.MasterTaskList.ExecuteTask(MasterTask currentTask)
at Master.Common.Task.MasterTaskList.InternalCalculation(Object sender,
DoWorkEventArgs e)
at System.ComponentModel.BackgroundWorker.OnDoWork(DoWorkEventArgs e)
at System.ComponentModel.BackgroundWorker.WorkerThreadStart(Object
argument)
at
System.Runtime.Remoting.Messaging.StackBuilderSink._PrivateProcessMessage(IntPtr
md, Object[] args, Object server, Int32 methodPtr, Boolean fExecuteInContext,
Object[]& outArgs)
at
System.Runtime.Remoting.Messaging.StackBuilderSink.PrivateProcessMessage(RuntimeMethodHandle
md, Object[] args, Object server, Int32 methodPtr, Boolean fExecuteInContext,
Object[]& outArgs)
at
System.Runtime.Remoting.Messaging.StackBuilderSink.AsyncProcessMessage(IMessage msg, IMessageSink replySink)
at
System.Runtime.Remoting.Proxies.AgileAsyncWorkerItem.ThreadPoolCallBack(Object o)
at System.Threading._ThreadPoolWaitCallback.WaitCallback_Context(Object
state)
at System.Threading.ExecutionContext.Run(ExecutionContext
executionContext, ContextCallback callback, Object state)
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback(Object
state)
 
Ad

Advertisements

J

Jon Skeet [C# MVP]

Matt said:
I'm having a problem with a static class constructor being called twice. I
have the static class MasterTaskList which uses a BackgroundWorker to execute
multiple methods on a separate thread. The static constructor calls a reset
function which creates a new instance of BackgroundWorker 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...
 
G

Guest

1) Nope, it's the following:

static MasterTaskList()
{
Trace.WriteLine("Static Constructor");
Trace.WriteLine(AppDomain.CurrentDomain.FriendlyName);
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 BackgroundWorker 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
 
J

Jon Skeet [C# MVP]

Matt said:
1) Nope, it's the following:

static MasterTaskList()
{
Trace.WriteLine("Static Constructor");
Trace.WriteLine(AppDomain.CurrentDomain.FriendlyName);
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 BackgroundWorker 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.
 
G

Guest

I got the test program to call the static constructor twice. What made it do
it was changing Environment.CurrentDirectory 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.
 
W

Walter Wang [MSFT]

Hi Matt,

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


Regards,
Walter Wang ([email protected], 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.
 
Ad

Advertisements

J

Jon Skeet [C# MVP]

I got the test program to call the static constructor twice. What made it do
it was changing Environment.CurrentDirectory 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.LoadFrom
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.LoadFrom needs to be pretty
careful :)

Here's the code:

----------------- Test.cs ---------------------
using System;
using System.IO;
using System.Collections.Generic;
using System.Reflection;

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

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

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

static void Main(string[] args)
{
Helper.Print("Starting");
if (Directory.Exists(PluginDirectory))
{
Directory.Delete(PluginDirectory, true);
}
Directory.CreateDirectory(PluginDirectory);

foreach (string x in args)
{
string target = Path.Combine(PluginDirectory, x);
File.Copy(x, target);
Assembly ass = Assembly.LoadFrom(target);
Type pluginType = ass.GetType("Plugin");
if (pluginType != null)
{
object plugin = Activator.CreateInstance(pluginType);
pluginType.GetMethod("Plug").Invoke(plugin, null);
}
}
}
}

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


Hope that helps,
Jon
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top