【问题标题】:Working with Actions and Task使用操作和任务
【发布时间】:2015-12-09 04:17:19
【问题描述】:

我想传入一个返回 void 并接受 param int 的方法。我该怎么做?

谁能告诉我非 lambda 和 lambda 版本?

        private void LongTask(int s)
        {

            Thread.Sleep(s* 1000);
        }


        public void Run()
        {
            Task q = Task.Run(Action<int>(LongTask));

        }

【问题讨论】:

  • lambda 会导致编译器为您创建一个闭包。如果必须,您可以自己创建闭包。基本上,创建一个类,将int s 存储为字段/属性,然后调用作用于int s 字段的void NameYourMethod(),在将其包装在Action 中后调用您传递给Task.Run ...但是你为什么不想使用 lambdas 呢?
  • 这个问题和多线程有什么关系?

标签: c# task-parallel-library


【解决方案1】:

在您的示例中,您没有显示要传递给该方法的值。这使得很难确定您甚至要问的是什么。但我认为您要求说明如何调用您的方法 LongTask(),将其传递给适当的值,作为 Task.Run() 操作,并使用 lambda 语法和非 lambda 语法。

注意:其中很少有具体针对 Task.Run()。这一切都适用于任何时候您将委托传递给一个方法,其中委托需要表示一个具有与您正在调用的方法中使用的签名不同的签名的方法。这里涉及到Task.Run() 几乎是偶然的。

不管怎样……

实际上,由于 lambda 表达式(在此用法中)编译为匿名方法,因此实际上有三个选项:

“老派”匿名方法:

public void Run()
{
    int someValue = 17;

    Task q = Task.Run(delegate { LongTask(someValue); });
}

上面导致编译器创建一个匿名方法(delegate() { LongTask(someValue); }),并生成代码来创建一个委托实例传递给Task.Run()方法。委托的类型是从用法推断出来的(即基于与匿名方法声明匹配的一种方法重载)。

Lambda 匿名方法:

public void Run()
{
    int someValue = 17;

    Task q = Task.Run(() => LongTask(someValue));
}

上面的例子和前面的例子一样,只是使用了名义上更简洁的 lambda 语法来声明匿名方法。我说“名义上”是因为在这种特殊情况下,它实际上并没有那么短。但在其他情况下,lambda 语法更方便;通常,当匿名方法返回一个值时,因为使用 lambda 语法,return 语句是隐式的。

显式:

class A
{
    private readonly int _value;
    private readonly Action<int> _action;

    public A(int value, Action<int> action)
    {
        _value = value;
        _action = action;
    }

    public void M() { _action(_value); }
}

public void Run()
{
    int someValue = 17;

    Task q = Task.Run((Action)(new A(someValue, LongTask).M));
}

以上依赖于一个显式编写的类,与匿名方法选项导致的编译器生成的类具有相同的目的。根据具体的场景,编译器生成的类在实现上可能会有很大的不同。但是行为本质上是相同的:创建了一个类的实例,其中存储了所需的值,并且在类中包含了一个具有适当签名的方法,其中该方法的主体是您要执行的代码.

(旁白:在这种情况下,为了简洁起见,我展示了一个实际采用委托实例而不是硬编码目标对象和方法调用的实现。这实际上是一个比编译器实际上会生成,主要是因为编译器必须处理更复杂的场景,这是一种仅在非常狭窄的场景中有用的优化,即只有一个方法被调用。编译器还将存储this并且会将匿名方法的主体复制到生成的方法,使用存储的this 引用和任何其他需要的捕获变量,例如调用LongTask() 方法。)

在上面的变量_valuereadonly,但在匿名方法中,它们通常是从匿名方法的上下文中捕获的可变变量。为了更好地模拟匿名方法通常的工作方式,您需要更多类似的东西:

class A
{
    public int value;
    private readonly Action<int> _action;

    public A(int value, Action<int> action)
    {
        this.value = value;
        _action = action;
    }

    public void M() { _action(value); }
}

public void Run()
{
    A a = new A(17, LongTask);

    Task q = Task.Run((Action)a.M);

    a.value = 19;
}

请注意,在上面,value 是一个公共字段。而代码实际上是在调用Task.Run()之后修改了变量。这类似于在匿名方法示例中更改someValue 的值(同样,在调用Task.Run() 之后),并且具有相同的风险:如果在任务可以执行该方法之前执行Task.Run() 方法之后的赋值调用,那么你会得到一个不同的值传递给方法,而不是任务可以先执行方法。

道德:始终注意您捕获的变量,并确保它们具有您期望的生命周期和价值。


顺便说一句,在最后两个示例中需要强制转换为所需的委托类型 Action,否则要选择的 Task.Run() 重载对编译器来说是不明确的,介于 Task.Run(Action)Task.Run(Func&lt;Task&gt;) 之间。短版:当尝试从方法组转换为委托类型并将其与特定重载匹配时,将忽略委托签名的返回类型。如需更长的版本,请参阅Compiler Ambiguous invocation error - anonymous method and method group with Func<> or Action

【讨论】:

    猜你喜欢
    • 2013-12-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-18
    • 2017-12-16
    相关资源
    最近更新 更多