在任务中维护引用?

时间:2016-07-22 15:14:20

标签: c# multithreading task

我已经整理了一个简单的演示类,如下所示:

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

    public Main()
    {
        var h = new HelloWorld() { Name = "A" };
        Task.Factory.StartNew(() => { Console.WriteLine(h.Name); });
        h = new HelloWorld() { Name = "B" };
    }

以下代码打印:

  

这完全合乎逻辑,但不是我想要的(我想打印A)。 我希望能够使用参数调用StartNew(),这将保留代理中h的第一个引用,但我无法看到此选项。

我错过了什么吗?

编辑:我可以看到我可以使用

Task.Factory.StartNew(new Action<object>((obj) => { Console.WriteLine((obj as Hello).Name); }),h);

虽然强迫传递object类型似乎有点.NET 1.1 / pre-generics,所以希望有更好的选择。

3 个答案:

答案 0 :(得分:1)

您遇到的内容称为闭包,它不是任务所特有的。每次在 lambda 中使用变量时,编译器都会在它为此目的构建的特殊类中捕获它。编译器大致生成类似的东西:

public void Main()
{
    var closure = new Main_Closure();
    closure.h = new HelloWorld() { Name = "A" };
    Task.Factory.StartNew(closure.M1);
    closure.h = new HelloWorld() { Name = "B" };
}

class Main_Closure
{
    public HelloWorld h;

    public void M1()
    {
        Console.WriteLine(h.Name); 
    }
}

由于closure.h可以在任务开始之前再次分配,因此您将获得您所看到的结果。

在这种情况下,您只需使用另一个变量来存储新对象。或者在调用lambda之前使用另一个变量,例如

var h1 = h;
Task.Factory.StartNew(() => { Console.WriteLine(h1.Name); });

答案 1 :(得分:0)

在方法中包装任务以防止关闭。

static Task DoAsync<T>(Action<T> action, T arg)
{
    return Task.Run(() => action(arg));
}

static void Main(string[] args)
{
    Action<HelloWorld> hello = (HelloWorld h2) => { Console.WriteLine(h2.Name); };

    var h = new HelloWorld() { Name = "A" };
    Task task = DoAsync(hello, h);
    var h = new HelloWorld() { Name = "B" };
}

答案 2 :(得分:-2)

您所遇到的是一种非常强大的机制,称为闭包。它在一系列环境中非常有用。更深入地了解闭包的工作原理:http://csharpindepth.com/Articles/Chapter5/Closures.aspx

您的案例中的问题是,在任务有机会运行之前,h会发生变化。请注意,这仅仅是运气,有时候任务可能先运行一些其他可能没有。

您可以做的一件事就是等待任务。

如果您的代码在主方法上,您可以通过在行尾添加.Wait()来实现此目的:

var h = new HelloWorld() { Name = "A" };
Task.Factory.StartNew(() => { Console.WriteLine(h.Name); }).Wait();
h = new HelloWorld() { Name = "B" };

如果你使用任何其他方法,你只需使用await关键字等待任务并使方法异步:

public async Task MyMethod()
{
    var h = new HelloWorld() { Name = "A" };
    await Task.Factory.StartNew(() => { Console.WriteLine(h.Name); });
    h = new HelloWorld() { Name = "B" };
}

另请注意,在大多数情况下,使用Task.Factory.StartNew不是最佳选择。尝试改为使用Task.Run。