With hangfire we can do something like:
public MyClass
{
public void RunJob()
{
SomeClass x = new SomeClass();
x.SomeProperty = "Test";
Hangfire.BackgroundJob.Enqueue(() => x.SomeMethod());
}
}
Imagine that SomeMethod
is not static, and depends upon the value of SomeClass.SomeProperty. If SomeMethod
is not static, how can this work? Object x
will go out of scope two lines later. Does Hangfire increase the reference count to object x
and the garbage collector doesn't clean up the object until Hangfire is done with it?
Further, imagine if SomeClass
implements IDisposable and SomeMethod
depends upon resources cleaned up by Dispose:
public MyClass
{
public void RunJob()
{
using (SomeClass x = new SomeClass())
{
x.SomeProperty = "Test";
Hangfire.BackgroundJob.Enqueue(() => x.SomeMethod());
}
}
}
In this case x.Dispose would likely be called before the job is run. Does this cause a bug?
In testing I can see that private member SomeClass._disposed is set to true following the using statement, but when Hangfire calls SomeMethod
seconds later, SomeClass._disposed is set to false! How does this black magic happen!?
Observations
I modified the class to include:
public MyClass
{
public static int LastId = 0;
private static int Id = 0;
public MyClass()
{
Id = ++LastId;
}
public void RunJob()
{
using (SomeClass x = new SomeClass())
{
x.SomeProperty = "Test";
Hangfire.BackgroundJob.Enqueue(() => x.SomeMethod());
}
}
}
... using this I can observe that the Id
of the class used by Hangfire is not the same instance used in my first example. So I assume Enqueue makes a copy of object x, or maybe the () => operator does.
In testing I can see that private member SomeClass._disposed is set to true following the using statement, but when Hangfire calls SomeMethod seconds later, SomeClass._disposed is set to false! How does this black magic happen!?
Because both x
are not the same objects. The one ran by hangfire is the result of the deserialization of the one ran by the main program which has been serialized upon enqueing.
In this case x.Dispose would likely be called before the job is run. Does this cause a bug?
This may cause a bug. For example, if running hangfire in the same appdomain as the main program and if disposing x
also disposes a shared resource which would be used by the x
deserialized by hangfire. Disposing objects which are part of the enqueing action seems like a code smell to me, because these objects are meant to be serializable.
Take a look at the Enqueue
method. You will notice that it takes an Expression<Action>
as parameter (and not just an Action
).
This expression will get serialized as well as all its parameters and constants and be deserialized upon dequeueing by Hangfire.