【问题标题】:Is it safe to access VT data from the other thread?从另一个线程访问 VT 数据是否安全?
【发布时间】:2011-04-07 19:07:31
【问题描述】:

从辅助线程更改 VirtualTreeView 数据是否安全? 如果是,我应该使用临界区(甚至是同步方法)吗?

我担心当我从另一个线程写入 VT 的数据记录时,主线程同时调用它的重绘,这个刷新会导致一次读取相同的记录。我会补充一下,我在应用程序中只使用了 2 个线程。

类似...

type
  PSomeRecord = ^TSomeRecord;
  TSomeRecord = record
    SomeString: string;
    SomeInteger: integer;
    SomeBoolean: boolean;
end;

...
var FCriticalSection: TRTLCriticalSection; // global for both classes
...

procedure TMyCreatedThread.WriteTheTreeData;
var CurrentData: PSomeRecord;
begin
  EnterCriticalSection(FCriticalSection); // I want to protect only the record

  CurrentData := MainForm.VST.GetNodeData(MainForm.VST.TopNode);

  with CurrentData^ do // I know, the ^ is not necessary but I like it :)
    begin
      SomeString := 'Is this safe ? What if VT will want this data too ?';
      SomeInteger := 777;
      SomeBoolean := True;
    end;

  LeaveCriticalSection(FCriticalSection);

  MainForm.VST.Invalidate;
end;

// at the same time in the main thread VT needs to get text from the same data
// is it safe to do it this way ?

procedure TMainForm.VST_GetText(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
  var CellText: string);
var CurrentData: PSomeRecord;
begin
  EnterCriticalSection(FCriticalSection); // I want to protect only the record

  CurrentData := VST.GetNodeData(VST.TopNode);

  with CurrentData^ do
    begin
      case Column of
        0: CellText := SomeString;
        1: CellText := IntToStr(SomeInteger);
        2: CellText := BoolToStr(SomeBoolean);
      end;
    end;

  LeaveCriticalSection(FCriticalSection);
end;

// I'm afraid the concurrent field reading may happen only here with the private VT fields
// FNodeDataSize, FRoot and FTotalInternalDataSize, since I have Node.Data locked by the
// critical sections in the VT events, some of those may be accessed when VT is refreshed
// somehow

function TBaseVirtualTree.GetNodeData(Node: PVirtualNode): Pointer;
begin
  Assert(FNodeDataSize > 0, 'NodeDataSize not initialized.');

  if (FNodeDataSize <= 0) or (Node = nil) or (Node = FRoot) then
    Result := nil
  else
    Result := PByte(@Node.Data) + FTotalInternalDataSize;
end;

更新

我已经在代码中添加了关键部分,从 TMyCreatedThread 类调用 GetNodeData 真的不安全吗,即使这个函数只返回一个指向记录的指针?

非常感谢

问候

【问题讨论】:

    标签: multithreading delphi synchronization critical-section virtualtreeview


    【解决方案1】:

    不,尤其是你这样做的方式。

    VST 是一个可视化控件。 MainForm 也是如此,您直接从您的线程中引用它。 GUI 控件不是线程安全的,不应直接从线程访问。此外,您指的是线程中的全局变量“MainForm”。这绝对不是线程安全的。

    如果您需要从主窗体和单独的线程访问VST 的数据,请不要将其直接存储在VST.Node.Data 中。将其保存在一个外部列表中,您可以用关键部分或其他一些线程安全方法将其包围,并在线程中访问该外部列表(首先锁定它)或在VST 事件中访问您的主窗体。请参阅 Delphi RTL 中的 TLockList 以获取可锁定列表的示例,并参阅 TMultipleReadExclusiveWrite 以获取示例同步类。

    【讨论】:

    • 谢谢,我明白你的意思,但从理论上讲,如果我只是将这两种方法都包含在我更新的问题中的关键部分块中会发生什么?我知道 GUI 控件不是线程安全的,但 GetNodeData 函数只返回指向节点数据的指针,而节点又是一个指针。正如我在某处读到的,Invalidate 是线程安全的。
    • 从线程中引用MainForm从不安全的。因为Node.Data 是一个指针,所以没有任何区别;它是一个指向您试图从多个线程不安全地访问的内存块的指针。 MainForm 也是一个指针;编译器只是对你隐藏了这个事实。与Invalidate无关;它与以可能导致数据或内存不稳定或损坏的方式访问的事物有关,这种方式可能导致难以追踪问题。当你可以做对而不做更多的工作时,为什么会做错呢?
    • > 它是一个指向您试图从多个线程不安全地访问的内存块的指针。 - 那是我看不到的,为什么不安全?我在 CriticalSection 块中访问它们,所以它们不应该与读取 GetNodeData 中使用的 FNodeDataSize、FRoot 和 FTotalInternalDataSize 冲突还是我错了?我很可能会按照你的建议使用公共列表变量,但我想知道为什么在 TMyCreatedThread 中调用 VST.GetNodeData 很危险。
    • 如果你的代码永远不会改变,它可能是安全的(现在)。但是有更好的方法可以做到这一点,并且从您的代码中我看不出有任何不同的理由。 (除非,也就是说,您已经为您的问题删除了很多代码以节省空间。)我是这样看的:有时有很多方法可以做事;使用已发布的方法几乎总是更好,因为这样可以很好地保护您免受更改。如果可以选择两种方式——例如,Windows API 或未记录的 DLL 调用),我将使用已发布的 API,没有理由不这样做。
    • 读取这些字符串字段也是不安全的。如果两个线程可以同时访问同一个字符串,那么该字符串的引用计数需要至少为 2。如果两个线程都通过同一个 variable 访问该字符串(它们在这个情况下,因为任何节点只有一条数据记录),那么引用计数将只有 1。当任一线程访问该线程并看到引用计数为 1 时,它将假定它对字符串具有独占访问权。跨度>
    猜你喜欢
    • 1970-01-01
    • 2019-02-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-14
    相关资源
    最近更新 更多