【问题标题】:Where are CLR-defined methods like [delegate].BeginInvoke documented? [closed][delegate].BeginInvoke 等 CLR 定义的方法记录在哪里? [关闭]
【发布时间】:2017-10-17 20:05:59
【问题描述】:

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

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

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

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

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

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

谢谢

【问题讨论】:

  • 听起来像一个更适合 MSDN 论坛的问题
  • 您的问题是“为什么微软的文档很烂?”如果是这样,那么这是一个咆哮。有实际问题吗?
  • 在您提到的 MSDN 文章中搜索“ThreadPool”。这一点很清楚。
  • 也许我没抓住重点,但 BeginInvoke 的全部目的是支持线程启动框架,这些线程取自 ThreadPool,而大多数 MS 文档都留下 很多需要,他们确实很清楚地提到了这一点,我认为......?没有得到咆哮。
  • 大卫,这不是咆哮。问题是:规范在哪里?

标签: c# documentation msdn begininvoke


【解决方案1】:

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 委托对象,它的目标将在异步调用完成时自动调用。第二个参数是一个可选的object,它将按原样传递给回调,用于跟踪状态。其他参数是动态生成的,并与委托声明相匹配。

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

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

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

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

【讨论】:

【解决方案2】:

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

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

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

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

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

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

MSDN 上提到的Delegate 是一个类。 让我们来看看这段代码(取自 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# 有这个匿名方法,它可以用来调用一个方法,而不必实际将它写成一个方法。

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

使用系统; // 声明委托——定义所需的签名: 委托无效 SampleDelegate(字符串消息);

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 线程池将持有对该操作的引用,您将泄漏一些内存。因此,在指定的回调方法中调用 EndInvoke 始终是一个好习惯。

我希望现在(不是全部)清除一些想法。

【讨论】:

  • 感谢您提供更多信息。里希特一如既往地是一位非常出色的作家。
猜你喜欢
  • 2013-02-21
  • 2013-05-25
  • 1970-01-01
  • 2011-04-15
  • 2011-11-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多