OmniThreadLibrary - 代码:1816。没有足够的配额可用于处理此命令

时间:2014-08-03 10:05:45

标签: delphi delphi-xe4 omnithreadlibrary

更新1 :我包含了所有线程的堆栈跟踪而不仅仅是主线程 - 我认为已经足够了。

更新2 :我重新打开了这个问题,因为即使应用了我自己的问题中说明的更改后,我今天仍然会收到相同的错误报告...

更新3 :似乎错误发生在线程终止时,错误发生时发送的线程消息是COmniTaskMsg_Terminated。现在它非常奇怪 - 我已经在程序中使用线程安全队列替换了几乎所有对Task.Comm.Send()的调用,所以我非常确定线程消息的数量经过{ {1}}非常小,远远小于填充Windows消息队列...


我使用Delphi XE4使用OmniThreadLibrary-3.03a(刚刚升级到最新的svn,我会看到......),并从最终用户报告错误,错误消息和线程的堆栈跟踪那个问题包含在最后。

实际上,通过将后台线程用来将日志发送到主线程的线程安全队列替换为Task.Comm的调用,我减少了出现此错误的可能性。现在我不知道在哪里看,最可能的地方在这里(但在此代码块中没有Task.Comm.Send()调用...):

Task.Comm.Send()

Stacktrace:

  //at this point, we are already in a thread other than the main thread.
  //I've two instances of the very same thread running when the program runs.
  myTasks := Parallel.ParallelTask.NumTasks(aTaskCount);// aTaskCount = 5

  myTasks.Execute(
    //the following anonymous method will be executed in sub-threads and will have
    //multiple instances determined by the aTaskCount param.
    procedure (const aSubTask: IOmniTask)
    begin
      //note: this code block runs in multiple sub-threads in parallel.
      //aUidList can have tens of thousands (or even more) of item.
      while aUidList.Take(uid) and (not Self.task.CancellationToken.IsSignalled) do
      begin
        ThreadedDoSomething();
      end;
    end
  );//END sub-thread

参考this answer of another question,似乎OTL用作通信通道的Windows消息队列已满。

4 个答案:

答案 0 :(得分:4)

尝试调用PostMessage后,OTL会引发异常。

procedure TOmniContainerWindowsMessageObserverImpl.Send(aMessage: cardinal;
  wParam, lParam: integer);
begin
  Win32Check(PostMessage(cwmoHandle, aMessage, wParam, lParam));
end;

该异常表示具有句柄cwmoHandle的窗口的消息队列已满,并且未及时得到服务。分配给TOmniMessageQueue属性时,此窗口的句柄由OnMessage创建。很可能是这个分配的线程被阻止(或阻塞太长时间)并且没有及时处理消息。

procedure TOmniMessageQueue.SetOnMessage(const value: TOmniMessageQueueMessageEvent);
begin
  if (not assigned(mqWinMsgObserver.OnMessage)) and assigned(value) then begin // set up observer
    mqWinMsgObserver.Window := DSiAllocateHWnd(WndProc);  // CREATED HERE**
    mqWinMsgObserver.Observer := CreateContainerWindowsMessageObserver(
      mqWinMsgObserver.Window, MSG_CLIENT_MESSAGE, 0, 0);
    ContainerSubject.Attach(mqWinMsgObserver.Observer, coiNotifyOnAllInserts);
    mqWinMsgObserver.Observer.Activate;
  end
  else if assigned(mqWinMsgObserver.OnMessage) and (not assigned(value)) then begin // tear down observer
    mqWinMsgObserver.Observer.Deactivate;
    ContainerSubject.Detach(mqWinMsgObserver.Observer, coiNotifyOnAllInserts);
    FreeAndNil(mqWinMsgObserver.Observer);
    DSiDeallocateHWnd(mqWinMsgObserver.Window);
  end;
  mqWinMsgObserver.OnMessage := value;
end;

为了更具体地回答,我们需要查看更多代码,以显示实现OTL对象的位置和方式。唯一的另一个线索是你的帖子就是这个;

thread $1160 (TOmniThread): <priority:-15>

这表明引发异常的线程正在以较低的优先级运行。除了您的应用程序正在积极改变线程优先级之外,这本身并不能解释任何其他事情。没有代码可以分析,没有办法说,但程序的结构可能会导致Priority Inversion问题,这会使拥有线程处理消息的能力受挫。

答案 1 :(得分:1)

这是我的解决方案 - 适用于遇到同样问题的人。但我接受了@J ..是答案,因为他帮助我找到了这个解决方案。

var
  waitEvent : IOmniCancellationToken;
begin
  // create the 'end of all sub-threads' signal
  waitEvent := CreateOmniCancellationToken;

  myTasks.NoWait.Execute(//Note the NoWait call
  procedure (const aSubTask: IOmniTask)
  begin
    doSomeDownloadTasks()
  end
  ).OnStop(procedure
  begin
    waitEvent.Signal;//once all subthreads are stopped, signal the parent thread.
  end
  );

  // wait for the sub-threads to stop, and while waiting, clear the Windows messages periodically
  while not waitEvent.IsSignalled do
  begin
    doSomeDataSavingTasks();

    //self.Task has a hidden Window handle, here we constantly empty its message queue
    //, to aovid the 'Not enough quota is available to process this command' error.
    Task.Comm.Receive(myMsg);
  end;

答案 2 :(得分:1)

我也有这个问题,在我的情况下,我忘记删除一些task.comm.send(),之后我从管道阶段删除了IOmniTaskConfig接口,以便进行调试。

另一方面,如果出现错误,您可以致电MsgData._ReleaseAndClear;以释放TOmniCommunicationEndpoint.SendWait()内的消息。

procedure TPmOTLTaskConfigLogger.OnMessage(const ATask: IOmniTaskControl;
  const AMsg: TOmniMessage);
begin
  try
    //your code
  finally
    AMsg.MsgData._ReleaseAndClear;
  end;
end;

以下SSCCE提出超过&#34;超出配额&#34;使用管道的异常(有时一些AV和&#34;队列已满且#34;异常)。超出的报价由otSharedInfo_ref.Monitor.Send()中的TOmniTask.InternalExecute()触发。

program sscce_otl_quota_exceeded_2;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  OtlCommon,
  OtlCollections,
  OtlTask,
  OtlParallel;

procedure Stage1(const input, output: IOmniBlockingCollection; const task: IOmniTask);
var
  LInputValue: TOmniValue;
begin
  for LInputValue in input do
    task.Comm.Send(1, LInputValue.AsInteger);//fill the queue
end;

var
  FPipeline: IOmniPipeline;
  LOutputValue: TOmniValue;
  i: Integer;
begin
  try
    FPipeline := Parallel.Pipeline
      //using multiple thread provokes the "Quota exceeded" exception after some
      //"Queue is full" exceptions and AVs. The "Quota exceeded" is triggered by
      //otSharedInfo_ref.Monitor.Send() in TOmniTask.InternalExecute().
      //Note: Sometimes the debugger hangs (XE3)
      .Stage(Stage1).NumTasks(16)
      .Run;

    for i := 1 to 9999 do
      FPipeline.Input.Add(i);

    FPipeline.Input.CompleteAdding;

    while not FPipeline.WaitFor(10) do
      Sleep(1);

    FPipeline := nil;
    Writeln('End');
    ReadLn;
  except
    on E: Exception do begin
      Writeln(E.ClassName, ': ', E.Message);
      ReadLn;
    end;
  end;
end.

答案 3 :(得分:0)

我们也遇到了“配额超出”和“队列已满”的例外情况。我们通过实例化我们自己的监视器并在其上调用进程消息来解决该问题。

我们正在创建自己的任务并持有对任务控制器的引用。在此我们将直接使用通信通道发送和接收消息。

//Setup 
FTaskCtrl := CreateTask(taskObj, name)
                .SetTimer(TASK_MSG_PUMP_TIMER_ID, TASK_MSG_PUMP_INTERVAL, DEBUG_MSG_PUMP)
                .OnTaskTerminated(OnTaskTerminated)
                .OnTaskMessage(OnTaskMessage);

//Sending messages to the task
FTaskCtrl.Comm.Send(CONST_MSG_NUM, TOmniValue.CastFrom<TOurMsgObj>(msgObjToSend));

//Getting messages from the task, done once every 100ms
FTaskCtrl.Comm.Receive(msgObj);

请注意,计时器消息是一条心跳消息,表示工作人员任务仍处于活动状态。

在我们的应用程序线程的主循环或业务引擎线程中,我们将“泵”通信以确保处理所有消息。泵呼叫大约每100ms调用一次。

procedure MainClass.Pump;
var
  msg : TOmniMessage;
begin
  if FTaskCtrl.Comm.Receive(msg) then
    OnTaskMessage(FTaskCtrl,msg);
end;

我们发现这会在队列中留下太多消息,所以我们将其更改为。

procedure MainClass.Pump;
var
  msg : TOmniMessage;
begin
  while FTaskCtrl.Comm.Receive(msg) do
    OnTaskMessage(FTaskCtrl,msg);
end;

这会产生突发行为。此外,我们仍然会在任务被终止时得到错误。关闭中的任务发送的特定终止消息未被传递。

我们发现即使我们没有创建监视器,也会在内部创建一个监视器。此外,它的wndproc没有被调用,或者至少有时间处理已经发送给它的消息。

因此最终我们回到了Omni线程的一些样本中实际看到的更简单的实现。

//Setup
FMonitor  := TOmniEventMonitor.Create(nil);
FMonitor.OnTaskTerminated := Self.OnTaskTerminated;
FMonitor.OnTaskMessage := Self.OnTaskMessage; 

FTaskCtrl := CreateTask(taskObj, name).SetTimer(DEBUG_TASK_MSG_PUMP_TIMER_ID, DEBUG_TASK_MSG_PUMP_INTERVAL, DEBUG_MSG_PUMP);

FTaskCtrl := FMonitor.Monitor(FTaskCtrl);


//Sending messages to the task
FTaskCtrl.Comm.Send(CONST_MSG_NUM, TOmniValue.CastFrom<TOurMsgObj>(msgObjToSend));


//Once per internal loop of the application/main logic thread. Once every 50ms.
FMonitor.ProcessMessages;

执行上述操作意味着调用了监视器wndproc并执行了所有逻辑。例如,我们看到终止消息已正确处理。

感谢J ...最初的帖子让我们朝着正确的方向前进。