如何防止OTL Pipeline密封输入?

时间:2016-01-23 00:40:59

标签: delphi delphi-xe2 omnithreadlibrary

很多年前,在旧论坛上,我问Primozh,如果Pipeline模式可以成为Uroboros,将半完整的结果反馈给自己。

当时Primozh表示这将是直截了当的,而PipeLine阶段不仅可以将OmniValues提供给OUTPUT,还可以输入INPUT。

问题是最初的喂食阶段运行速度太快,它们会过期并密封INPUT集合,因此无法联合密封它,因此只要它们尝试将半烤包送回自身 - 瞧! - OTL抛出“无法添加到已完成的集合”例外。

那么,如何通过自我馈送Pipeline模式实现上述链接的自我爆炸任务?

UPD:改变了“自爆”的例子 - 生成大量的中间半计算结果 - 排列生成,简单(希望)计算阶乘。然而,这具有决定性的缺点:它总是生成一个中间工作项,因此没有品尝管道处理不断增长的集合的能力。

{$A+} // not $A8
type FactTask = record
  Goal, Curr: Cardinal;
  Value : Int64;
end;

procedure TForm6.Button1Click(Sender: TObject);
var Msg: string;
    f: FactTask;
    Results: TArray<Int64>;
    pipeOut: IOmniBlockingCollection;
    pipe:    IOmniPipeline;
begin
  lblResults.Caption := ' WAIT, we are producing...';
  Repaint;

  pipe := Parallel.Pipeline;
  f.Goal := edLen.Value; // 10
  f.Curr := 0;
  f.Value := 1;

  pipe.Stage(
     procedure ( const input, output: IOmniBlockingCollection )
     begin
       output.Add( TOmniValue.FromRecord( f ) );
     end
  );

  pipe.Stage(
     procedure ( const input, output: IOmniBlockingCollection )
     var f_in, f_out: FactTask; v: TOmniValue;
     begin
       for v in input do begin
         f_in := v.ToRecord<FactTask>;
         if f_in.Curr < f_in.Goal then begin
            f_out.Goal := f_in.Goal;
            f_out.Curr := Succ(f_in.Curr);
            f_out.Value := f_in.Value * f_out.Curr;
            input.Add( TOmniValue.FromRecord( f_out ) ); //  <<< Exception!
         end;
       end;
     end
  );

  pipe.Stage(
     procedure ( const input, output: IOmniBlockingCollection )
     var f_in: FactTask;  v: TOmniValue;
     begin
       for v in input do begin
         f_in := v.ToRecord<FactTask>;
         if f_in.Curr = f_in.Goal then begin
            Output.Add( f_in.Value );
         end;
       end;
     end
  );

  pipe.Run;
  pipeOut := pipe.Output;
//    pipe.WaitFor(INFINITE);  ToArray would efficiently do that
//    pipeOut.CompleteAdding;    ...without frozing on Pipeline/Collections SetThrottle
  Results := TOmniBlockingCollection.ToArray<Int64>(pipeOut);

  Msg := IntToStr(f.Goal) + '! = ' + IntToStr(Results[0]);
  lblResults.Caption := Msg;
  ShowMessage(Msg);
end;

它与管道阶段崩溃,试图重新填充意外被TOmniPipeline.Run封锁的输入。 在标记的行中,意外地抛出了“无法添加到竞争集合”的异常。

当集合在空和少之间保持平衡时,如何保持管道运行(这不仅是起始条件,它会在计算结束时重复)?

有点做梦:https://plus.google.com/+AriochThe/posts/LCHnSCmZYtx
更多:https://github.com/gabr42/OmniThreadLibrary/issues/61

1 个答案:

答案 0 :(得分:2)

您的演示程序可以正常工作。

你的第一个阶段只输出一个记录到它的输出(顺便说一句,你可以通过写入pipe.Input在主线程中完成),然后关闭它的输出管道。

这反过来导致第二阶段关闭。在for v in input退出之前,第二阶段通常会尝试写入input(除非您对计时非常满意)。但是,input已经关闭,Add会引发异常。调用TryAdd代替Add将解决此问题。

我猜想Pipeline并不是你想要的抽象,而是通过使用别的东西会更好。我会使用正常的低级任务包装TOmniBlockingCollection(对于第2阶段)。您必须使用Create(1)创建此阻止集合,以便它知道它正在自行提供并自动取消阻止。 (有关详细信息,请参阅Parallel Search in a Tree中的the book示例。)