BeginThread 结构体 - Delphi

6

我现在有一个几乎完成的应用程序,下一个要实现的功能是线程。我选择使用BeginThread(),虽然我知道Delphi中也有TThread。我遇到的问题是BeginThread()调用的结构。通常情况下,调用我想要线程化的函数的程序行是

CompareFiles(form1.Edit3.Text,Form1.Edit4.Text,Form1.StringGrid2,op);

op是一个整数。

我替换掉它的那一行代码,用它来创建一个线程:

BeginThread(nil,0,CompareFiles,Addr('form1.Edit3.Text,Form1.Edit4.Text,Form1.StringGrid2,op'),0,x);

根据我查到的关于如何使用BeginThread()的信息,这应该是一个很好的调用方法,但是在编译时,我遇到了有关BeginThread()语句参数结构的编译器错误。

附加信息编辑:

当前调用CompareFiles的过程是

procedure TForm1.Panel29Click(Sender: TObject);
var
op,x : integer;

begin
    if (Form1.Edit3.Text <> '') AND (Form1.Edit4.Text <> '') then
        begin
          op := 3;
          if RadioButton7.Checked = True then op := 0;
          if RadioButton3.Checked = True then op := 1;
          if RadioButton4.Checked = True then op := 2;
          if RadioButton5.Checked = True then op := 3;
          if RadioButton6.Checked = True then op := 4;
          CompareFiles(form1.Edit3.Text,Form1.Edit4.Text,Form1.StringGrid2,op);
        end;
end;

如果像几个人建议的那样使用 TThread,并且像 Rob 展示的那样,我对如何传递 op、Edit3/4.Text 和 StringGrid2 到 CompareFiles 感到困惑。猜测从我看到的 TThread 示例中,我认为我应该用 TCompareFilesThread.Execute 替换上面的代码,然后将 Panel29Click 中的当前代码放入 TCompareFilesThread.Create,最后添加即可。

FEdit3Text := Edit3Text;
FEdit4Text := Edit4Text;
FGrid := Grid;

转化为此

FEdit3Text := Form1.Edit3.Text;
FEdit4Text := Form1.Edit4.Text;
FGrid := Form1.StringGrid2;

但我有一种无法言喻的感觉,完全错了。

“关于不匹配的投诉”并不是一个非常有用的描述。您能否编辑您的问题并粘贴一份错误信息的副本? - Mason Wheeler
2
我也很好奇为什么你选择使用TThread?在没有良好的对象封装的情况下使用全局线程函数会极大地复杂化与并发和竞争条件相关的事情。我也很好奇为什么你在接近应用程序完成时才决定添加线程。在我看来,线程应该从一开始就考虑,而不是事后想起。如果不足早期考虑,存在高概率的线程陷阱。 - Allen Bauer
线程现在被实现的原因是这是一个学习应用程序。线程一直都是计划中的,只是现在才开始实现,因为应用程序的核心功能已经完成(大部分)。至于TThread,我似乎找不到一个好的解释来说明如何使用它。 - Flatlyn
2
我在这里开始学习有关Delphi的线程:http://www.eonclash.com/Tutorials/Multithreading/MartinHarvey1.1/ToC.html 也许对你来说是一个不错的起点? - Bulan
不要自己调用 Execute 方法,线程会自动调用它。如果你自己调用它,那么你将从主线程中调用它,在这种情况下,你失去了在单独的线程中运行的所有优势。请考虑给你的窗体和控件赋予有意义的名称。你的计算机选择了现在拥有的名称,它们很糟糕。你是一个人,你知道得更好,所以根据它们的功能而不是按照你放置它们的顺序来命名它们。 - Rob Kennedy
1个回答

14

这绝非使用 BeginThread 的正确方法。该函数期望传递一个参数的函数指针,但你想调用的函数需要四个参数。你向 BeginThread 提供了一个字符串作为参数,但你显然希望某种魔法可以将该字符转换为变量所包含的值。

Delphi 并不是这样工作的,即使有些语言可以做到这样,实际上也通常不建议这样做。

要传递多个参数给 BeginThread,需要定义一个记录(record)来包含所有需要的值,并定义一个记录指针:

type
  PCompareFilesParams = ^TCompareFilesParams;
  TCompareFilesParams = record
    Edit3Text,
    Edit4Text: string;
    Grid: TStringGrid;
    Op: Integer;
  end;

CompareFiles更改为接受指向该记录的指针:

function CompareFiles(Params: PCompareFilesParams): Integer;
要启动线程,您需要分配该记录的实例并填充其字段:
var
  Params: PCompareFilesParams;
begin
  New(Params);
  Params.Edit3Text := Edit3.Text;
  Params.Edit4Text := Edit4.Text;
  Params.Grid := StringGrid2;
  Params.Op := op;
  BeginThread(nil, 0, @CompareFiles, Params, 0, x);

按照以下方式实现CompareFiles函数,以便记录在线程终止之前被释放:

function CompareFiles(Params: PCompareFilesParams): Integer;
begin
  try
    // <Normal implementation goes here.>
  finally
    Dispose(Params);
  end;
end;

如果您使用TThread,则可以使所有内容变得更加容易。您可以使您的派生类在其构造函数中拥有任意数量的参数,因此您不必费心动态分配和释放特殊记录。

type
  TCompareFilesThread = class(TThread)
  private
    FEdit3Text,
    FEdit4Text: string;
    FGrid: TStringGrid;
    FOp: Integer;
    procedure Execute; override;
  public
    constructor Create(const Edit3Text, Edit4Text: string; Grid: TStringGrid; Op: Integer);
    property ReturnValue;
  end;

constructor TCompareFilesThread.Create;
begin
  inherited Create(False);
  FEdit3Text := Edit3Text;
  FEdit4Text := Edit4Text;
  FGrid := Grid;
  FOp := Op;
end;

procedure TCompareFilesThread.Execute;
begin
  ReturnValue := CompareFiles(FEdit3Text, FEdit4Text, FGrid, FOp);
end;

不要调用 BeginThread,而是实例化该类并让它运行:

var
  ThreadRef: TThread;


ThreadRef := TCompareFilesThread.Create(Edit3.Text, Edit4.Text, StringGrid2, Op);

使用线程还有很多要注意的地方,比如要知道线程何时完成,但我认为你已经有足够的基础了。最后需要注意的一件事是,TStringGrid 是一个 VCL 控件,不能从这个新线程中对它进行任何操作(无论你是如何创建它的)。所有与网格控件相关的操作都必须在主线程中完成。使用 TThread.SynchronizeTThread.Queue 将所有 VCL 操作切换到主线程中执行。你的文件比较线程将等待同步操作完成,但它会继续运行而不会等待排队的操作完成。


非常感谢您的详细解释!但是"Delete(Params);" 应该改为 "Dispose(Params);"。 - arthurprs
@Arthurprs 哎呀!你能看出我整天都在用C++吗? - Rob Kennedy
如果我能弄清楚如何使用TThread,那么我会这样做。例如,在你提供的示例代码中,我应该如何在OnClick过程中调用它,以设置op并决定何时运行CompareFiles。 - Flatlyn
更新了原始帖子,提供了有关先前评论的更多信息。 - Flatlyn
@TheFlatline:TThread非常易于使用,并且有很多关于如何使用它的教程。只需从TThread子类化,创建一个构造函数来接收线程中所需的一切,并覆盖受保护的Execute方法即可。 - jpfollenius

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接