C#Parallel Extensions Task.Factory.StartNew调用错误对象的方法

时间:2009-11-21 06:48:40

标签: c# parallel-processing

好的,在System.Threading.Tasks中使用.Net 4.0 Parellel Extensions。我发现了什么似乎奇怪的行为,但我认为我做错了。我有一个界面和几个实现条款,它们很简单。

interface IParallelPipe
{
    void Process(ref BlockingCollection<Stream> stream, long stageId);
}

class A:IParallelPipe
{
    public void Process(ref BlockingCollection<Stream> stream, long stageId)
    {
        //do stuff
    }
}

class B:IParallelPipe
{
    public void Process(ref BlockingCollection<Stream> stream, long stageId)
    {
        //do stuff
    }
}

然后我的班级就这些开始了。这就是出现问题的地方。我基本上从传入的类型中获取有关要调用的实现类的信息,然后调用工厂来实例化它,然后用它创建一个任务并启动它。如图所示:

BlockingCollection<Stream> bcs = new BlockingCollection<Stream>();                   
foreach (Stage s in pipeline.Stages) 
{
    IParallelPipe p = (IParallelPipe)Factory.GetPipe(s.type);
    Task.Factory.StartNew(() => p.Process(ref bcs, s.id)); 
}

在我的示例的每次运行中,pipeline.Stages包含两个元素,一个被实例化为A类,另一个被视为B类。这很好,我在te调试器中看到它随着两个元素的消失不同种类。但是,B类永远不会被调用,而是我得到两个A.Process(...)方法的调用。两者都包含传入的stageId(即两个调用具有不同的stageIds)。

现在,如果我把一些东西分开来,只是为了测试我可以通过做这样的事情来使事情发挥作用:

BlockingCollection<Stream> bcs = new BlockingCollection<Stream>();                   
A a = null;
B b = null;
foreach (Stage s in pipeline.Stages) 
{
    IParallelPipe p = (IParallelPipe)Factory.GetPipe(s.type);
    if(p is A)
        a = p;
    else
        b = p;
}
Task.Factory.StartNew(() => a.Process(ref bcs, idThatINeed)); 
Task.Factory.StartNew(() => b.Process(ref bcs, idThatINeed));

这会调用相应的类!

有什么想法???

1 个答案:

答案 0 :(得分:4)

您所描述的行为对我来说似乎很奇怪 - 我希望使用正确的实例,但可能会使用错误的阶段ID - old foreach variable capture问题。正在捕获变量s,并且在任务工厂评估闭包时,s的值已更改。

这绝对是您的代码中的一个问题,但它并不能解释您遇到问题的原因。只是为了检查,你真的 在循环中声明p,而不是在它之外?如果你在循环之外声明p,这将解释所有内容。

以下是捕获问题的解决方法:

BlockingCollection<Stream> bcs = new BlockingCollection<Stream>();
foreach (Stage s in pipeline.Stages) 
{
    Stage copy = s;
    IParallelPipe p = (IParallelPipe)Factory.GetPipe(s.type);
    Task.Factory.StartNew(() => p.Process(ref bcs, copy.id)); 
}

请注意,我们只是在循环中复制并捕获该副本,以便每次都获得变量的不同“实例”。

或者,我们可以只捕获ID,而不是捕捉舞台,而不是我们需要的所有内容:

BlockingCollection<Stream> bcs = new BlockingCollection<Stream>();
foreach (Stage s in pipeline.Stages) 
{
    long id = s.id;
    IParallelPipe p = (IParallelPipe)Factory.GetPipe(s.type);
    Task.Factory.StartNew(() => p.Process(ref bcs, id)); 
}

如果这没有帮助,你能发布一个简短但完整的程序来证明这个问题吗?这样可以更容易追踪。