MVVM light Messenger 中的动作、局部变量和垃圾收集的奇怪行为

Strange behavior with actions, local variables and garbage collection in MVVM light Messenger(MVVM light Messenger 中的动作、局部变量和垃圾收集的奇怪行为)
本文介绍了MVVM light Messenger 中的动作、局部变量和垃圾收集的奇怪行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在 MVVM Light 中的 Messenger 系统遇到了一个非常奇怪的问题.这很难解释,所以这里有一个演示这个问题的小程序:

I am having a really strange problem with the Messenger system in MVVM Light. It's hard to explain, so here is small program that demonstrates the issue:

using System;
using GalaSoft.MvvmLight.Messaging;

namespace TestApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var prog = new Program();
            var recipient = new object();

            prog.RegisterMessageA(recipient);
            prog.RegisterMessageB(recipient);

            prog.SendMessage("First Message");
            GC.Collect();
            prog.SendMessage("Second Message");
        }

        public void RegisterMessageA(object target)
        {
            Messenger.Default.Register(this, (Message msg) =>
            {
                Console.WriteLine(msg.Name + " recieved by A");
                var x = target;
            });
        }

        public void RegisterMessageB(object target)
        {
            Messenger.Default.Register(this, (Message msg) =>
            {
                Console.WriteLine(msg.Name + " received by B");
            });
        }

        public void SendMessage(string name)
        {
            Messenger.Default.Send(new Message { Name = name });
        }

        class Message
        {
            public string Name { get; set; }
        }
    }
}

如果您运行应用程序,这是控制台输出:

If you run the application, this is the console output:

First Message recieved by A
First Message received by B
Second Message received by B

如您所见,收件人 A 从未收到第二条消息.但是,B 和 A 之间的唯一区别是一行:语句 var x = target;.如果删除此行,A 会收到第二条消息.

As you can see, the second message is never received by recipient A. However, the only difference between B and A is one line: the statement var x = target;. If you remove this line, A receives the second message.

此外,如果您删除 GC.Collect();,则 A 会收到第二条消息.但是,这只是隐藏了问题,因为在实际程序中,垃圾收集器最终会自动运行.

Also, if you remove GC.Collect(); then A receives the second message. However, this only hides the issue, as in a real program the garbage collector will automatically run eventually.

为什么会这样?我假设不知何故,如果接收者动作从它包含的方法范围引用一个变量,它会将动作的生命周期与该范围联系起来,这样一旦超出范围,它就可以被垃圾收集.我不明白为什么会这样.我也不明白为什么不从它们定义的范围内引用变量的操作没有这个问题.

Why is this happening? I assume that somehow, if the recipient action refers to a variable from it's containing method scope, it ties the action's lifetime to that scope so that once out of the scope it can be garbage collected. I don't understand why this is at all. I also don't understand why actions that do not reference variables from the scope they are defined in do not have this problem.

谁能解释这里发生了什么?

Can anyone explain what is going on here?

推荐答案

好吧,我现在明白为什么会这样了(我相信,无论如何).我以较短的形式复制了它,使用 lambda 表达式,然后我将解释为什么 lambdas 很重要.

Well, I now understand why it's happening (I believe, anyway). I've reproduced it in a shorter form which doesn't use lambda expressions, and then I'll explain why the lambdas are important.

using System;
using GalaSoft.MvvmLight.Messaging;

class Program
{
    static void Main(string[] args)
    {
        Receiver r1 = new Receiver("r1");
        Receiver r2 = new Receiver("r2");
        var recipient = new object();

        Messenger.Default.Register<object>(recipient, r1).ShowMessage;
        Messenger.Default.Register<object>(recipient, r2).ShowMessage;

        GC.Collect();
        Messenger.Default.Send(recipient, null);
        // Uncomment one of these to see the relevant message...
        // GC.KeepAlive(r1);
        // GC.KeepAlive(r2);
    }
}

class Receiver
{
    private string name;

    public Receiver(string name)
    {
        this.name = name;
    }

    public void ShowMessage(object message)
    {
        Console.WriteLine("message received by {0}", name);
    }
}

基本上,信使只保留对消息处理程序的弱引用.(对于接收者也是如此,但这不是问题.)更具体地说,它似乎对处理程序的 target 对象具有弱引用.它似乎并不关心委托对象本身,但目标很重要.所以在上面的代码中,当你保留一个 Receiver对象活着,仍然使用以该对象为目标的委托.但是,当允许对目标进行垃圾回收时,不使用使用该对象的处理程序.

Basically, the messenger only keeps a weak reference to the message handler. (Also to the recipient, but that's not a problem here.) More specifically, it appears to have a weak reference to the handler's target object. It doesn't seem to care about the delegate object itself, but the target is important. So in the above code, when you keep a Receiver object alive, the delegate which has that object as a target is still used. However, when the target is allowed to be garbage collected, the handler using that object is not used.

现在让我们看看你的两个处理程序:

Now let's look at your two handler:

public void RegisterMessageA(object target)
{
    Messenger.Default.Register(target, (Message msg) =>
    {
        Console.WriteLine(msg.Name + " received by A");
        var x = target;
    });
}

这个 lambda 表达式捕获 target 参数.为了捕获它,编译器生成了一个新类 - 所以 RegisterMessageA 是有效的:

This lambda expression captures the target parameter. In order to capture it, the compiler generates a new class - so RegisterMessageA is effectively:

public void RegisterMessageA(object target)
{
    GeneratedClass x = new GeneratedClass();
    x.target = target;
    Messenger.Default.Register(x.target, x.Method);
}

private class GeneratedClass
{
    public object target;

    public void Method(Message msg)
    {
        Console.WriteLine(msg.Name + " received by A");
        var x = target;
    }
}

现在,除了让 GeneratedClass 的实例保持活动状态的委托之外,别无其他.将其与您的第二个处理程序进行比较:

Now, there's nothing other than the delegate which keeps that instance of GeneratedClass alive. Compare that with your second handler:

public void RegisterMessageB(object target)
{
    Messenger.Default.Register(target, (Message msg) =>
    {
        Console.WriteLine(msg.Name + " received by B");
    });
}

这里没有捕获变量,所以编译器生成的代码有点像这样:

Here, there are no captured variables, so the compiler generates code a bit like this:

public void RegisterMessageB(object target)
{
    Messenger.Default.Register(target, RegisterMessageB_Lambda);
}

private static void RegisterMessageB_Lambda(Message msg)
{
    Console.WriteLine(msg.Name + " received by B");
}

这里是一个 static 方法,所以根本没有委托目标.如果委托捕获了 this,它将作为实例方法生成.但重要的一点是不需要生成额外的类......所以没有什么可以被垃圾收集.

Here it's a static method, so there's no delegate target at all. If the delegate captured this, it would be generated as an instance method. But the important point is that there's no need to generate an extra class... so there's nothing to be garbage collected.

我还没有仔细研究 MvvmLight 是如何做到这一点的 - 是否 只是 获得了对委托的弱引用,以及 CLR 正在以某种特殊方式处理它,或者 MvvmLight 是否是将目标与委托本身分开.无论哪种方式,我希望这可以解释您所看到的行为.关于如何解决您在真实代码中看到的任何问题 - 基本上,您需要确保对您需要的任何委托目标保持强引用.

I haven't looked into exactly how MvvmLight is doing this - whether it's just got a weak reference to the delegate, and that the CLR is treating that in some special way, or whether MvvmLight is separating the target from the delegate itself. Either way, I hope that explains the behaviour you're seeing. In terms of how to fix whatever problem you're seeing with real code - basically you'll need to make sure you keep a strong reference to whatever delegate target you need.

好的,现在看来是由于 WeakActionGeneric 及其基类 WeakAction.我不知道这种行为是否是预期的行为(作者),但这是负责的代码:)

Okay, it looks like it's now due to WeakActionGeneric and its base class WeakAction. I don't know whether this behaviour is the expected behaviour (by the author), but that's the code responsible :)

这篇关于MVVM light Messenger 中的动作、局部变量和垃圾收集的奇怪行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!

本站部分内容来源互联网,如果有图片或者内容侵犯您的权益请联系我们删除!

相关文档推荐

Is there a runtime benefit to using const local variables?(使用 const 局部变量有运行时的好处吗?)
How to avoid #39;Unassigned Local Variable#39; defined inside a try-catch block(如何避免在try-Catch块中定义未赋值的局部变量)
When does a local variable inside a function *actually* gets allocated(函数内的局部变量何时*实际*被分配)
How to remove compiler error with struct: quot;Use of unassigned local variablequot;(如何使用 struct 删除编译器错误:“使用未分配的局部变量)
Why does this (null || !TryParse) conditional result in quot;use of unassigned local variablequot;?(为什么这个 (null || !TryParse) 条件会导致“使用未分配的局部变量?)
Why does adding local variables make .NET code slower(为什么添加局部变量会使 .NET 代码变慢)