我正在开发一个多线程C#Windows应用程序,它经常调用本机dll。阻塞调用有时会持续很长时间。
在某些情况下,我想在主线程的某些工作线程上取消这些阻塞调用我正在使用的本机API为此提供了一个函数:
HRESULT CancelBlockingCall(DWORD ThreadID)
尽管CancelBlockingCall()的文档并不十分清楚,但我相信我需要为操作系统级别的线程传递ThreadID。基于我从CancelBlockingCall()得到的返回码,我意识到Thread.ManagedThreadID不是我需要的。我在msdn (see the Note)上找到了以下内容:
操作系统ThreadId与托管线程没有固定的关系,因为 非托管主机可以控制托管和非托管线程之间的关系。 具体来说,复杂的主机可以使用CLR Hosting API来安排许多托管 针对同一操作系统线程的线程,或者在两者之间移动托管线程 不同的操作系统线程。
这是否意味着我无法为托管线程正确调用CancelBlockingCall()?是否无法确定托管线程当前正在执行的OS级线程的ThreadId?
答案 0 :(得分:14)
正如其他人提到的那样,您可以在调用阻塞本机函数并在某处注册该ID之前尝试调用GetCurrentThreadId
,但这是一个定时炸弹 - 有一天您的托管线程将被抢占并重新启动调度到两个p / invoke调用之间的不同OS级别线程。我建议的唯一可靠方法是编写一个非常小的非托管代理dll,它将首先调用GetCurrentThreadId
(将其写入托管代码可见的某个out IntPtr
),然后是本机阻塞功能。回调托管代码而不是out IntPtr
也可能有效;当堆栈中存在非托管帧时,CLR很难重新调度线程。
编辑:显然你不是第一个遇到此类问题的人:System.Threading.Thread
中有两个方便的方法可以让我们解除我提到的时间炸弹和p / invoke { {1}}:Thread.BeginThreadAffinity()
和Thread.EndThreadAffinity()
。 CLR主机不会将托管线程重新调度到这些调用之间的其他本机线程。但是,您的代码需要以高信任级别运行才能调用这些方法。
答案 1 :(得分:4)
这是否意味着我无法为托管线程正确调用CancelBlockingCall()?是否无法确定托管线程当前正在执行的OS级线程的ThreadId?
正如Aidan所说,您可以使用GetCurrentThreadID API来获取操作系统线程ID。
要在托管线程中跟踪它,您可以将API调用包装在存储操作系统线程ID的类中,以便以后可以停止它:
public class APITask
{
private uint _osThreadId;
public void Run()
{
_osThreadId = GetCurrentThreadID();
API.RunBlockingMethod();
}
public void Cancel()
{
API.CancelBlockingCall(_osThreadId);
}
}
答案 2 :(得分:2)
您可以尝试P /调用GetCurrentThreadID API
答案 3 :(得分:0)
如何使用Abortable ThreadPool?
来自文章;
相反,请考虑一个线程池实现,它为您提供排队的工作项的cookie。然后,池还可以提供一个Cancel方法,该方法将采用其中一个cookie并取消关联的工作项,将其从队列中删除或在适当时中止执行的工作项。为此任务实现自定义线程池可能不是最好的主意,但还有其他选择