11

[编辑,完全改写:] 似乎我的问题确实措辞不佳,也很糟糕。所以我希望这个完整的改写有助于......

MSDN 明确规定: Control.BeginInvoke()在创建控件句柄的线程上执行委托,通常这将是 GUI 线程。Dispatcher.BeginInvoke()将在创建 Dispatcher 对象的线程上运行。这将是我创建的任何线程。

但是对于委托,“ CLR 自动定义BeginInvoke 和 EndInvoke”,而这些调用在 ThreadPool 线程上运行。除了这种稍微令人惊讶的不同行为之外,我想知道如何找到自动实现的所有功能的规范。

例如:Intelli-sense 显示我的委托有一个 DynamicInvoke()。类System.Delegate{}确实有一个 DynamicInvoke() 这可能意味着我的委托继承了它。但是 Delegate{} 没有 BeginInvoke()。而且 Delegate{} 有几个我的委托没有的功能。我的委托也获得了一个 GetObjectData() 方法。这似乎来自ISerializable。

所以总而言之,一个委托似乎从(1)CLR“自动”获取它的方法,(2)Delegate的某个子集{}可能是MulticastDelegate{},可能是(3)ISerializble。我在哪里可以找到委托获得的所有方法的全面规范?特别有趣的是 BeginInvoke(),它是精确的签名,因为前面提到的具有该名称的两个方法具有不同的签名集。

[有人在编辑中建议“代表”是“代表”。我敢说,它不是。]

谢谢

4

2 回答 2

21

Control.Begin/End/Invoke() 和 Dispatcher.Begin/End/Invoke() 方法与委托的 Begin/End/Invoke() 方法具有相同的名称和类似的行为,但最好放弃他们的想法是相同的。最重要的区别是委托的方法是类型安全的,这在 Control 和 Dispatcher 版本中完全没有。运行时行为也非常不同。

CLI 规范ECMA 335第 II.14.6 章详细说明了管理委托的规则。最好读一章,我只给出一个概要。

委托声明被转换为继承自 MulticastDelegate 的类(不是 CLI 规范中指定的委托)。该类始终只有 4 个成员,它们的运行时实现由 CLR 提供:

  • 一个接受一个对象和一个 IntPtr 的构造函数。对象是Delegate.Target,IntPtr是目标方法Delegate.Method的地址。这些成员稍后在您调用委托时使用,如果委托绑定到的方法是实例方法,则 Target 属性提供this引用,对于静态方法,则为 null。Method 属性确定调用哪个方法。您不直接指定这些参数,编译器会在您使用 new 运算符或使用 += 运算符订阅事件处理程序时提供它们。在事件的情况下有很多语法糖,您不必显式使用new运算符。

  • 一个 Invoke() 方法。该方法的参数是动态生成的,并且与委托声明相匹配。调用 Invoke() 方法在同一线程上运行委托目标方法,即同步调用。您很少在 C# 中使用它,您只需使用允许通过仅使用对象名称和括号来调用委托对象的语法糖。

  • BeginInvoke() 方法,提供了一种进行异步调用的方法。该方法在目标方法忙于执行时快速完成,类似于 ThreadPool.QueueUserWorkItem 但具有类型安全的参数。返回类型始终为 System.IAsyncResult,用于找出异步调用何时完成并提供给 EndInvoke() 方法。第一个参数是一个可选的 System.AsyncCallback 委托对象,它的目标将在异步调用完成时自动调用。第二个参数是一个可选对象,它将按原样传递给回调,用于跟踪状态。附加参数是动态生成的,并与委托声明相匹配。

  • EndInvoke() 方法。它接受一个 IAsyncResult 类型的参数,您必须传递从 BeginInvoke() 获得的参数。它完成异步调用并释放资源。

您在委托对象上看到的任何其他方法都是从基类 MulticastDelegate 和 Delegate 继承的方法。像 DynamicInvoke() 和 GetObjectData()。

异步调用是棘手的,你很少需要使用它们。它们实际上在 .NETCore 目标(如 Silverlight)中不可用。委托目标方法在任意线程池线程上运行,就像 Threadpool.QueueUserWorkItem() 一样。它可能抛出的任何未处理的异常都会被捕获并终止线程,但不会终止您的程序。必须调用 EndInvoke(),这样做会导致 10 分钟的资源泄漏。如果目标方法抛出异常,那么当您调用 EndInvoke() 时它将重新引发。您无法控制线程池线程,无法取消或中止它。Task 或 Thread 类是更好的选择。

MSDN 是相关的,委托类型的方法没有记录。它假设您从规范和委托声明中了解它们的作用以及它们的外观。

于 2013-02-21T23:07:19.930 回答
8

根据您问题的主题,答案将是这些粗线。MSDN 可能不会更好,但它很好:)

杰弗里·里希特(Jeffrey Richter)写过您在上面的问题中提出的问题。他在 MSDN 杂志上有这篇文章。 http://msdn.microsoft.com/en-us/magazine/cc164139.aspx 本文将向您展示实际(可能不是实际上但非常接近)BeginInvoke 和 EndInvoke 实际上是如何在 .NET 中实现的CLR。在本文中花一些时间,之后我认为您现在不需要提前阅读。 Jeffrey Richter 在他的 CLR Via C# 一书中也很好地解释了这一点。

大多数 UI 应用程序都是单线程的。UI 上的控件只能使用创建它们的线程来访问。

为了实现这个 Control.Invoke 存在于 Winforms 中。它将自动在 UI 线程上调用您的代码。在 WPF 世界中,我们没有 Control.Invoke。在 WPF 中,我们使用 Dispatcher 而不是 Control。

现在委托与委托。 Hans Passant 提供了一个非常好的答案。

因此,为了深入了解它,我正在写这个答案。

MSDN 上提到的委托是一个类。让我们来看看这段代码(取自 msdn http://msdn.microsoft.com/en-us/library/ms173171(v=vs.80).aspx

public delegate int PerformCalculation(int x, int y);

正如您在此处看到的,我们有委托(注意带有小“d”)。这是定义委托的关键字,或者简单地说,这是定义变量 PerformCalculation 的关键字,该变量实际上包含对方法的引用。

我认为您已经意识到这一点,但只是为了完整性。

现在使用此变量并使用如下代码调用方法:

using System;
// Declare delegate -- defines required signature:
delegate void SampleDelegate(string message);

class TestDelegate
{
    private void CallMeUsingDelegate(string m_param)
    {
        Console.WriteLine("Called me using parameter - " + m_param);
    }

    public static void Main(string[] args)
    {
        // Here is the Code that uses the delegate defined above.
        SampleDelegate sd = new SampleDelegate(CallMeUsingDelegate);
        sd.Invoke("FromMain");
    }
}

现在要调用一个方法,您需要编写一个完整的方法作为上面的 CallMeUsingDelegate 方法。C# 具有这种匿名方法,可用于调用方法而无需将其实际编写为方法。

所以上面的代码也可以写成

使用系统;// 声明委托——定义所需的签名:delegate void SampleDelegate(string message);

class TestDelegate
{
    public static void Main(string[] args)
    {
        // Here is the Code that uses the delegate defined above.
        SampleDelegate sd = delegate(param) {
                        Console.WriteLine("Called me using parameter - " + param);
                    };

        sd.Invoke("FromMain");
    }
}

这与上面的代码做同样的工作。但是现在我们需要少写一些代码。编译器将为上述两个版本创建相同的 IL 代码。但在第 2 种情况下,新方法将具有编译器自动生成的名称。

对于 BeginInvoke 和 EndInvoke,它们用于异步调用方法。这是使用 CLR 提供的线程池完成的。

基本上会发生什么是你调用一个方法使用

IAsyncResult ar = sd.BeginInvoke(CallMeUsingDelegate, callMeOnCompletion, sd);

这里 Delegate 是您正在调用的方法。将会发生的情况是,您的程序的 Thread 将调用 BeginInvoke 方法,该方法将在内部调用 CLR ThreadPool 线程上的 Delegate 参数中指定的方法。然后你的程序继续运行并返回一个实现 IAsyncResult 接口的对象。您可以使用此对象查询使用您的委托调用的任务的进度(注意作为 3 参数传递的委托 sd)。

CallMeUsingDelegate 方法在单独的线程(ThreadPool 的)上调用。当任务完成时,ThreadPool 将调用指定为 2 参数的 Callback 方法。

看着这一切你可能会想,为什么我们需要 EndInvoke 呢???

这是因为如果你不调用 EndInvoke,CLR ThreadPool 将持有对这个操作的引用,你会泄漏一些内存。因此,在指定的回调方法中调用 EndInvoke 始终是一个好习惯。

我希望这现在可以清除(不是全部),而是一些想法。

于 2013-02-24T17:14:09.953 回答