9

I've been writing things like this in my implementations:

public void SomeMethod(int someValue, List<int> someValues)
{
  Task generatedTask = null;

  {
    int anotherValue = 2;
    object valuesRef = someValues;
    generatedTask = new Task(delegate{
      anotherValue += someValue + GetSum(valuesRef);
      Console.WriteLine(anotherValue);
    });
  }

  generatedTask.Start();
}

However, I don't know exactly what's happening here...

Maybe everything was "copied" to the delegate. Or maybe, like reference types, all value types will have a copy associated to the Task delegate until it exists?

I'm just trying to understand what exactly happens in latest C# versions for performance matters.

4

3 回答 3

8

很好的问题;捕获的变量和闭包上下文。反编译显示当前编译器在这里创建了2 个捕获上下文对象:

public void SomeMethod(int someValue, List<int> someValues)
{
    Task task;
    <>c__DisplayClass3 class2; // <== compiler generated type; unpronounceable
    <>c__DisplayClass1 class3; // <== compiler generated type; unpronounceable
    class3 = new <>c__DisplayClass1(); // outer-scope context
    class3.someValue = someValue;
    task = null;
    class2 = new <>c__DisplayClass3(); // <== inner-scope context
    class2.CS$<>8__locals2 = class3; // <== bind the contexts
    class2.anotherValue = 2;
    class2.valuesRef = someValues;
    task = new Task(new Action(class2.<SomeMethod>b__0));
    task.Start();
    return;
}

如果您的目标是最小化上下文对象,您可以手动执行闭包:

public void SomeMethod2(int someValue, List<int> someValues)
{
    Task generatedTask = null;
    {
        var ctx = new MyCaptureContext();
        ctx.anotherValue = 2;
        ctx.valuesRef = someValues;
        ctx.someValue = someValue;
        generatedTask = new Task(ctx.SomeMethod);
    }

    generatedTask.Start();
}

class MyCaptureContext
{
    // kept as fields to mimic the compiler
    public int anotherValue;
    public int someValue;
    public object valuesRef;
    public void SomeMethod()
    {
        anotherValue += someValue + GetSum(valuesRef);
        Console.WriteLine(anotherValue);
    }
}

您还可以通过缓存单独传入状态的单个委托来避免为每个任务创建委托:

public void SomeMethod(int someValue, List<int> someValues)
{
    Task generatedTask = null;
    {
        var ctx = new MyCaptureContext();
        ctx.anotherValue = 2;
        ctx.valuesRef = someValues;
        ctx.someValue = someValue;
        generatedTask = new Task(MyCaptureContext.SomeMethod, ctx);
    }

    generatedTask.Start();
}
class MyCaptureContext
{
    // kept as fields to mimic the compiler
    public int anotherValue;
    public int someValue;
    public object valuesRef;
    public static readonly Action<object> SomeMethod = SomeMethodImpl;
    private static void SomeMethodImpl(object state)
    {
        var ctx = (MyCaptureContext)state;
        ctx.anotherValue += ctx.someValue + GetSum(ctx.valuesRef);
        Console.WriteLine(ctx.anotherValue);
    }
}

或(清洁工,IMO):

public void SomeMethod(int someValue, List<int> someValues)
{
    Task generatedTask = null;
    {
        var ctx = new MyCaptureContext();
        ctx.anotherValue = 2;
        ctx.valuesRef = someValues;
        ctx.someValue = someValue;
        generatedTask = ctx.CreateTask();
    }

    generatedTask.Start();
}
class MyCaptureContext
{
    // kept as fields to mimic the compiler
    public int anotherValue;
    public int someValue;
    public object valuesRef;
    public Task CreateTask()
    {
        return new Task(someMethod, this);
    }
    private static readonly Action<object> someMethod = SomeMethod;
    private static void SomeMethod(object state)
    {
        var ctx = (MyCaptureContext)state;
        ctx.anotherValue += ctx.someValue + GetSum(ctx.valuesRef);
        Console.WriteLine(ctx.anotherValue);
    }
}
于 2013-07-29T08:45:57.410 回答
1

你在谈论闭包。查看这篇文章以了解幕后发生的事情。

于 2013-07-29T08:50:12.060 回答
1

对此的技术术语是“闭包”:绑定到声明它的环境的函数。

该函数(在本例中为匿名任务委托)绑定到父函数的环境,并且可以访问其父函数的变量,就好像它们是它自己的一样。

可以在这篇优秀的博客文章中找到更完整的解释,但这里有一个简单的例子:

public void SomeMethod()
{
    Task generatedTask = null;

    {
        int someValue = 2;

        generatedTask = new Task(delegate{
            Console.WriteLine(someValue);
        });
    }

    someValue = 3;

    generatedTask.Start(); // Will write "3" to the console
}

在幕后,C# 编译器将创建一个新类来保存闭包上下文(someValue本示例中的变量),并使匿名委托成为此类的实例方法。

于 2013-07-29T08:45:36.063 回答