我已经整理了一个简单的演示类,如下所示:
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,所以希望有更好的选择。
答案 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。