TTask.Waitforall生成"线程数组中的至少一个任务nil"错误

时间:2017-12-22 18:27:52

标签: android delphi

我在Android服务项目中使用Delphi 10.1.2。我已经按照可用的示例来管理TTask.Run线程。我使用Array of ITaskTList<ITask>尝试了代码,但错误是相同的。

{$define TASK_ARRAY}

{$ifdef TASK_ARRAY}
  TaskArray: Array of ITask; 
{$else}
  TaskList: TList<ITask> // has the same problem
{$endif}

线程的创建是异步的,实际上是由GPS驱动的......

procedure TMainService.SendNewPosition(Username: String; NewLocation: TLocationCoord2D;
  PosTimeStamp: TDateTime; UseColor: TAlphaColor);
var
  ATask: ITask;
begin
...

ATask := TTask.Create(procedure
begin
  // a small procedure that runs very quickly
  // but which is called very frequently
end
);
if ATask <> nil then // trying to eliminate the problem
begin
  {$ifdef TASK_ARRAY}
    SetLength(TaskArray, Length(TaskArray) + 1);
    TaskArray[High(TaskArray)] := ATask;
  {$else}
    TaskList.Add(ATask);
  {$endif}
  ATask.Start;
end;

稍后,在AndroidServiceTrimMemory事件处理程序中,我执行此操作:

   var
     i: Integer;
     ATask: ITask;
   begin
     try
       {$ifdef TASK_ARRAY}
         i := High(TaskArray);
         // try to delete any nil ITask before WaitForAll finds it
         while i > -1 do
         begin
           ATask := ITask(TaskArray[i]);
           if ATask = nil then // never happens
           begin
             Delete(TaskArray,i,1); 
             i := High(TaskArray);
           end
           else
             Dec(i);
         end;
         TTask.WaitForAll(TaskArray, Wait);
         SetLength(TaskArray, 0);
       {$else}
         TTask.WaitForAll(TaskList.ToArray, Wait);
         Tasklist.Clear;
       {$endif}
     except
     on e: exception do
       Log(e.Message); // 'At least one task in array nil'
     end;

现在我有机会追踪到TTask.WaitforAllTTask.DoWaitForAll,以查看引发异常的位置:

for I := High(Tasks) downto Low(Tasks) do
begin
  Task.Value := TTask(Tasks[I]);
  if Task.Value = nil then
    raise EArgumentNilException.CreateRes(@sWaitNilTask);

当引发错误时,I有时为1,有时为0. High(Tasks)可能是4或5.每次运行都会发生错误。

所有评论都表示赞赏。

**在Remi的评论之后编辑**

我将system.threading复制到我的项目中并对DoWaitForAll进行了一些小改动(带超时的重载)

var
  ATask: ITask;
...
  for I := High(Tasks) downto Low(Tasks) do
  begin
    ATask := ITask(Tasks[I]); // casts correctly
    if ATask <> nil then
    begin
      Task.Value := TTask(Tasks[I]); // doesn't cast correctly
      if Task.Value = nil then
        raise EArgumentNilException.CreateRes(@sWaitNilTask);

这有点过头了。但也许其他人明白这里发生了什么?

1 个答案:

答案 0 :(得分:-1)

如果有人关注这个问题,我只想描述我采用的解决方案。即。这是我的应用问题的答案,而不是我的问题的答案。我使用了以下代替Array of ITaskTaskList: TList<ITask>的代码:

FThreads: TObjectList<TThread>;

...

constructor TMainService.Create(Owner: TComponent);
...
  FThreads := TObjectList<TThread>.Create(True);
  AThread := TThread.CreateAnonymousThread(procedure
  begin
    // this dummy thread does nothing
  end
  );
  AThread.FreeOnTerminate := False;
  FThreads.Add(AThread); // so ARC won't throw FThreads away (it happened!)

...

procedure TMainService.SendNewPosition(Username: String; NewLocation: TLocationCoord2D;
PosTimeStamp: TDateTime; UseColor: TAlphaColor);
var
  AThread: TThread;
  ...
  AThread := TThread.CreateAnonymousThread(
  procedure
  begin
    // a small procedure that runs very quickly
    // but which is called very frequently   
  end
  );
  AThread.FreeOnTerminate := False;
  FThreads.Add(AThread);
  AThread.Start;

稍后,在AndroidServiceTrimMemory事件处理程序中,我这样做:

i := 1; // preserve the dummy thread reference
while i < FThreads.Count do
begin
   AThread := FThreads.Items[i];
   AThread.WaitFor;
   Inc(i);
end;
// delete all but the dummy thread [0]
while FThreads.Count > 1 do
begin
  i := FThreads.Count - 1;
  FThreads.Delete(i);
  FThreads.TrimExcess;
end;