Lazy: “The function evaluation requires all thr

2019-01-14 07:17发布

问题:

I have a static class with some static properties. I initialized all of them in a static constructor, but then realized that it is wasteful and I should lazy-load each property when needed. So I switched to using the System.Lazy<T> type to do all the dirty work, and told it to not to use any of its thread safety features since in my case execution was always single threaded.

I ended up with the following class:

public static class Queues
{
    private static readonly Lazy<Queue> g_Parser = new Lazy<Queue>(() => new Queue(Config.ParserQueueName), false);
    private static readonly Lazy<Queue> g_Distributor = new Lazy<Queue>(() => new Queue(Config.DistributorQueueName), false);
    private static readonly Lazy<Queue> g_ConsumerAdapter = new Lazy<Queue>(() => new Queue(Config.ConsumerAdaptorQueueName), false);

    public static Queue Parser { get { return g_Parser.Value; } }
    public static Queue Distributor { get { return g_Distributor.Value; } }
    public static Queue ConsumerAdapter { get { return g_ConsumerAdapter.Value; } }
}

When debugging, I noticed a message I've never seen:

The function evaluation requires all threads to run

Before using Lazy<T>, the values were displayed directly. Now, I need to click on the round button with the threads icon to evaluate the lazy value. This happens only on my properties that are retrieving the .Value of Lazy<T>. When expanding the debugger visualizer node of the actual Lazy<T> object, the Value property simply displays null, without any message.

What does that message mean and why is it displayed in my case?

回答1:

I've found an MSDN page titled "How to: Refresh Watch Values" explaining it:

When you evaluate an expression in the debugger, one of two refresh icons might appear in the Value column. One refresh icon is a circle that contains two arrows, which circle in opposite directions. The other is a circle that contains two wavy lines that resemble threads.

...

If the two threads appear, the expression was not evaluated because of a potential cross-thread dependency. A cross-thread dependency means that evaluating the code requires other threads in your application to run temporarily. When you are in break mode, all threads in your application are typically stopped. Allowing other threads to run temporarily can have unexpected effects on the state of your program and causes the debugger to ignore events such as breakpoints.

I'd still like a better explanation if anyone can give it. Questions that this doesn't answer include: What kind of evaluation requires all threads to run? How does the debugger identify such a case? What exactly happens when you click the thread refresh icon?

EDIT: I think I've stumbled across the answer when examining Lazy<T> under ILSpy (for a completely different reason). The getter of the Value property has a call to a Debugger.NotifyOfCrossThreadDependency(). MSDN has this to say:

[...] performing a function evaluation typically requires freezing all threads except for the thread that is performing the evaluation. If the function evaluation requires execution on more than one thread, as might occur in remoting scenarios, the evaluation will block. The NotifyOfCrossThreadDependency notification informs the debugger that it has to release a thread or abort the function evaluation.

So basically, to prevent the annoying case where you try to evaluate some expression and Visual Studio just hangs for 30 seconds and then informs you that "a function evaluation has timed out", the code has a chance to inform the debugger that it must unfreeze other threads for the evaluation to succeed or otherwise the evaluation will block forever.

Since running other threads may disrupt your debugging session, as usually when you evaluate an expression all other threads are kept frozen, the debugger doesn't automatically proceeed and warns you before letting you jump down the rabbit hole.



回答2:

My guess would be that the debugger is trying to avoid influencing the application state by loading the properties for you.

You have to remember, that lazy load only happens when you reference/access the properties.

Now, in general, you do not want debugging to affect the state of the application, otherwise that will not give an accurate representation of what the application state should be (Think multi threaded apllications and debugging)

Have a look at Heisenbug



回答3:

Create a local variable and assign it the value you want to inspect.

This will let you inspect it because the debugger doesn't have to worry about whether or not accessing the property will disturb your application, because it will have already accessed it when assigning it to the local variable.



回答4:

I struggled with this for hours and found the original error message about requiring all threads to run misleading. I was accessing an existing database from a new solution and creating new Entity Framework entity POCOs and data access layers within the new solution to access and map to the DB.

I did two things initially wrong. I didn't properly define the primary key in my C# entity POCO, and the table I was accessing had a unique schema in the DB (it was not dbo.tablename but edi.tablename).

In my DbContext.cs file, I did the following to map the table under the right schema. Once I corrected these things the error went away and it worked just fine.

protected override void OnModelCreating(DbModelBuilder dbModelBuilder)
{
    base.OnModelCreating(dbModelBuilder);
    dbModelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
    dbModelBuilder.Entity<TableName>().ToTable("TableName", schemaName: "EDI");
}


回答5:

For me, I found it didn't matter if I had this.Configuration.LazyLoadingEnabled = false; or = true;, if I had the line in my DBContext or not. It seems to occur, from my reading up on the problem, because a thread is occurring & the debugger wants permission to run it/is warning you before it spurs it. Apparently, in some cases, you can even allow it to proceed according to MUG4N's answer here: Visual Studio during Debugging: The function evaluation requires all threads to run

But what I found was I could get around the issue.

2 options:

  1. Add .ToList() on your Queues:

    var q = db.Queues.OrderBy(e => e.Distributor).ToList();

  2. I found a workaround by selecting Non-Public Members > _internalQuery > ObjectQuery > Results View.