草庐IT

关于多线程:在Delphi中检测辅助线程上下文

codeneng 2023-03-28 原文

Detecting secondary thread context in Delphi

在 Delphi 2009 和 Windows API 中,有没有办法检测特定代码段是否在辅助线程的上下文中运行?在伪代码中,我想说:

1
2
3
4
5
6
7
procedure DoSomething;
begin
  if InvokedBySecondaryThread then
    DoIt_ThreadSafeWay
  else
    DoIt_RegularWay;
end;

这是一个我编写并使用多年的日志库,现在我正在尝试适应可以从多个线程调用一个过程的情况。我的"常规方式"不是线程安全的。我知道如何使它成为线程安全的,但我只想在实际需要时才使用线程安全方法。

解释(非必读:-)

它归结为使用 SendMessage 和 PostMessage 将记录的消息发送到多个接收器之间的选择,例如日志文件、控制台或 VCL 控件。使用 PostMessage 意味着在长时间的阻塞操作正在进行时将不会收到消息,这在一定程度上违背了记录的目的,尤其是。用于指示进度时。我想我可以用一个关键部分来保护 SendMessage 调用,但我更愿意只在真正需要时才这样做。

我知道 system.pas 中的全局变量 IsMultiThread,但这只会告诉我应用程序已启动辅助线程。这些可能是由 3rd 方库创建的线程,因此它们永远不会访问"我的"代码,因此它们的存在不会影响我的日志记录逻辑。

我真的希望我可以使用相同的低级库代码,无论它是从一个线程还是多个线程调用。例如,从辅助线程内部调用修改后的线程安全日志记录过程会很容易,但这会重复很多代码,而且我仍然必须记住始终做正确的事情。

@Lieven:目前,日志记录逻辑是这样的,有点简化

我希望日志记录尽可能轻松,只需最少的设置代码并且不用担心管理对象的生命周期,因此该库只公开了一些重载的帮助程序,例如

1
2
3
procedure Log( const msgText : string; level : TLogLevel = lvNotice ); overload;
procedure Log( const msgText : string; Args : array of const; level : TLogLevel = lvNotice ); overload;
etc, including specialized routines that log a StringList, a boolean, an Exception and so on

几乎所有其他事情都发生在单元的实现中。所有的辅助例程最终都会调用

1
procedure _LogPostMessage( const msgText : string; level : TLogLevel );

which (a) 检查单例调度程序对象是否已初始化; (b) 创建 TLogMessagePacket 对象的实例(消息文本、时间戳等的容器),最后 (c) 执行 SendMessage 或 PostMessage 以将"数据包"发送到调度程序(它有一个窗口句柄收到)。

然后是一组从抽象 TLogReceiver 类派生的类。一个这样的类接收记录的消息并将它们写入文件,另一个更新 TMemo 等。我实例化我想在项目中使用的具体接收器,并将它们注册到调度程序,调度程序从那时起拥有它们。

当调度程序收到消息"数据包"时,它会依次将它交给每个接收者,然后释放它。

所以我可能固定在上述思维方式中,这就是为什么当你说将调度程序对象交给日志库的代码应该根据线程上下文选择一个时,我不太明白你的想法。调度器确实是库的主要引擎,一次只存在一个。

  • 我认为你没有抓住重点。如果您的例程在多线程应用程序的主线程中调用,并且您选择运行非线程安全代码,则它有可能产生冲突(以它不是线程安全的方式)。我想你想知道的是调用例程的应用程序是否是多线程的。


1
2
3
4
5
6
  if GetCurrentThreadID = MainThreadID then begin
// in main thread
  end
  else begin
// in secondary thread
  end;

  • 1 但 OP 应该考虑将此类结构排除在他的日志库之外。 IMO,应该为库提供一个分发消息的对象。将调度程序对象传递给日志库的代码应根据线程上下文选择一个。
  • @Lieven:我不确定我是否正确地想象您提出的建议,但我对更好的解决方案感兴趣。我将向 OP 添加对当前日志记录流程的快速说明。
  • 在(尝试)进行 TDD 时,我可能会因为过于强调"我将如何测试这个,让我们使用 DI"而感到内疚,并且忘记了所有关于 KISS 的事情。您的编辑清楚地表明,使用(两个)单独的对象执行实际的 Post/SendMessage 几乎没有增加价值。


应您的要求,我添加了一些示例代码来说明我的意思。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface
  procedure Log( const msgText : string; level : TLogLevel = lvNotice; dispatcher: IDispatcher = nil); overload;
...

implementation    

  procedure Log( const msgText : string; level : TLogLevel = lvNotice; dispatcher: IDispatcher = nil); overload;
  var
    dp: IDispatcher;
  begin
    if dispatcher = nil then dp := CreateDefaultDispatcher
    else dp := dispatcher;

    dp.msgText := msgText;
    dp.level := level;
    ...
  end;

procedure _LogPostMessage( dispatcher: IDispatcher );
begin
  dispatcher.LogPostMessage            
end;

现在这给你带来了什么?

除了已经指出的增加了复杂性之外,并没有太多,但它使_LogPostMessage 的测试更容易。

这样看:你将如何编写单元测试来验证消息确实被发布了?

有关关于多线程:在Delphi中检测辅助线程上下文的更多相关文章

  1. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  2. ruby-on-rails - 如何使辅助方法在 Rails 集成测试中可用? - 2

    我在app/helpers/sessions_helper.rb中有一个帮助程序文件,其中包含一个方法my_preference,它返回当前登录用户的首选项。我想在集成测试中访问该方法。例如,这样我就可以在测试中使用getuser_path(my_preference)。在其他帖子中,我读到这可以通过在测试文件中包含requiresessions_helper来实现,但我仍然收到错误NameError:undefinedlocalvariableormethod'my_preference'.我做错了什么?require'test_helper'require'sessions_hel

  3. ruby - 如何让Ruby捕获线程中的语法错误 - 2

    我正在尝试使用ruby​​编写一个双线程客户端,一个线程从套接字读取数据并将其打印出来,另一个线程读取本地数据并将其发送到远程服务器。我发现的问题是Ruby似乎无法捕获线程内的错误,这是一个示例:#!/usr/bin/rubyThread.new{loop{$stdout.puts"hi"abc.putsefsleep1}}loop{sleep1}显然,如果我在线程外键入abc.putsef,代码将永远不会运行,因为Ruby将报告“undefinedvariableabc”。但是,如果它在一个线程内,则没有错误报告。我的问题是,如何让Ruby捕获这样的错误?或者至少,报告线程中的错误?

  4. ruby-on-rails - Controller 中的 Rails 辅助模块 - 2

    我有一个Controller,我想为这个Controller创建一个助手,我可以在不包含它的情况下使用它。我尝试像这样创建一个与Controller同名的助手classCars::EnginesController我创建的助手是moduleCars::EnginesHelperdefcheck_fuellogger.debug("chekingfuel")endend我得到的错误是undefinedlocalvariableormethod`check_fuel'for#有没有我遗漏的约定? 最佳答案 如果你真的想在Controll

  5. ruby - 如何在 ruby​​ 中运行后台线程? - 2

    我是ruby​​的新手,我认为重新构建一个我用C#编写的简单聊天程序是个好主意。我正在使用Ruby2.0.0MRI(Matz的Ruby实现)。问题是我想在服务器运行时为简单的服务器命令提供I/O。这是从示例中获取的服务器。我添加了使用gets()获取输入的命令方法。我希望此方法在后台作为线程运行,但该线程正在阻塞另一个线程。require'socket'#Getsocketsfromstdlibserver=TCPServer.open(2000)#Sockettolistenonport2000defcommandsx=1whilex==1exitProgram=gets.chomp

  6. ruby - 在 Ruby 中,在类方法的上下文中,什么是实例变量和类变量? - 2

    如果我有以下一段Ruby代码:classBlahdefself.bleh@blih="Hello"@@bloh="World"endend@blih和@@bloh到底是什么?@blih是Blah类中的一个实例变量,@@bloh是Blah类中的一个类变量,对吗?这是否意味着@@bloh是Blah的类Class中的一个变量? 最佳答案 人们似乎忽略了该方法是类方法。@blih将是常量Bleh的类Class实例的实例变量。因此:irb(main):001:0>classBlehirb(main):002:1>defself.blehirb

  7. ruby - Rails 开发服务器、PDFKit 和多线程 - 2

    我有一个使用PDFKit呈现网页的pdf版本的Rails应用程序。我使用Thin作为开发服务器。问题是当我处于开发模式时。当我使用“bundleexecrailss”启动我的服务器并尝试呈现任何PDF时,整个过程会陷入僵局,因为当您呈现PDF时,会向服务器请求一些额外的资源,如图像和css,看起来只有一个线程.如何配置Rails开发服务器以运行多个工作线程?非常感谢。 最佳答案 我找到的最简单的解决方案是unicorn.geminstallunicorn创建一个unicorn.conf:worker_processes3然后使用它:

  8. ruby - 在 Ruby 的 if 语句中检查 bash 命令 - 2

    如何在Ruby的if语句中检查bash命令的返回值(true/false)。我想要这样的东西,if("/usr/bin/fswscell>/dev/null2>&1")has_afs="true"elsehas_afs="false"end它会提示以下错误含义,它总是返回true。(irb):5:warning:stringliteralincondition正确的语法是什么?更新:/usr/bin/fswscell寻找afs安装和运行状态。它会抛出这样的字符串,Thisworkstationbelongstocell如果afs没有运行,命令以状态1退出 最

  9. ruby-on-rails - 关于 Ruby 的一般问题 - 2

    我在我的rails应用程序中安装了来自github.com的acts_as_versioned插件,但有一段代码我不完全理解,我希望有人能帮我解决这个问题class_eval我知道block内的方法(或任何它是什么)被定义为类内的实例方法,但我在插件的任何地方都找不到定义为常量的CLASS_METHODS,而且我也不确定是什么here,并且有问题的代码从lib/acts_as_versioned.rb的第199行开始。如果有人愿意告诉我这里的内幕,我将不胜感激。谢谢-C 最佳答案 这是一个异端。http://en.wikipedia

  10. ruby - Ruby 1.9.1 中的 native 线程,对我有什么好处? - 2

    所以,Ruby1.9.1现在是declaredstable.Rails应该与它一起工作,并且正在慢慢地将gem移植到它。它具有native线程和全局解释器锁(GIL)。自从GIL到位后,原生线程是否比1.9.1中的绿色线程有任何优势? 最佳答案 1.9中的线程是原生的,但它们被“放慢了速度”,一次只允许一个线程运行。这是因为如果线程真的并行运行,它会混淆现有代码。优点:IO现在在线程中是异步的。如果一个线程阻塞在IO上,那么另一个线程将继续执行直到IO完成。C扩展可以使用真正的线程。缺点:任何非线程安全的C扩展都可能存在使用Thre

随机推荐