views:

664

answers:

4

I'm working on a multi-threaded C# Windows application that makes frequent calls into a native dll. These are blocking calls which can sometimes last quite a long time.

In certain situations, I'd like to cancel these blocking calls on some worker threads from the main thread The native API I'm using provides a function for this purpose:

HRESULT CancelBlockingCall(DWORD ThreadID)

Although the documentation for the CancelBlockingCall() isn't terribly clear, I believe I need to pass the ThreadID for the OS-level thread which is blocking on the call. Based on the return codes I'm getting from CancelBlockingCall(), I realized that Thread.ManagedThreadID is not what I need. I found the following on msdn (see the Note):

An operating-system ThreadId has no fixed relationship to a managed thread, because an unmanaged host can control the relationship between managed and unmanaged threads. Specifically, a sophisticated host can use the CLR Hosting API to schedule many managed threads against the same operating system thread, or to move a managed thread between different operating system threads.

Does this mean that there is no way for me to properly call CancelBlockingCall() for a managed thread? Is it impossible to determine the ThreadId of the OS-level thread in which a managed thread is currently executing?

+2  A: 

You could try P/Invoking the GetCurrentThreadID API

Aidan Ryan
+3  A: 

Does this mean that there is no way for me to properly call CancelBlockingCall() for a managed thread? Is it impossible to determine the ThreadId of the OS-level thread in which a managed thread is currently executing?

As Aidan said, you can use the GetCurrentThreadID API to get the OS thread ID.

To keep track of it across managed threads, you could wrap your API calls in a class where you store the OS Thread ID, so that you can stop it later :

public class APITask
{
    private uint _osThreadId;

    public void Run()
    {
        _osThreadId = GetCurrentThreadID();
        API.RunBlockingMethod();
    }

    public void Cancel()
    {
        API.CancelBlockingCall(_osThreadId);
    }
}
Thomas Levesque
A: 

How about using Abortable ThreadPool?

From the article;

Instead, consider a thread pool implementation that handed you back a cookie of sorts for a queued work item. The pool could then also provide a Cancel method that would take one of these cookies and cancel the associated work item, removing it from the queue or aborting the executing work item as appropriate. Implementing a custom thread pool for this task probably isn't the best idea, but there are other alternatives

Khurram Aziz
+6  A: 

As other people mentioned, you could try p/invoking GetCurrentThreadId before calling the blocking native function and registering that id somewhere, but this is a timebomb — some day your managed thread will get pre-empted and re-scheduled to a different OS-level thread between the two p/invoke calls. The only reliable way I can suggest is to write a tiny unmanaged proxy dll, which will call first GetCurrentThreadId (writing it into an out IntPtr someplace where it will be visible to managed code) and then your native blocking function. A callback into managed code instead of out IntPtr might work too; CLR can hardly re-schedule threads while there are unmanaged frames on the stack.

Edit: apparently you're not the first person to have such problems: there are two handy methods in System.Threading.Thread which allow one to defuse the timebomb I mentioned and p/invoke GetCurrentThreadId(): Thread.BeginThreadAffinity() and Thread.EndThreadAffinity(). CLR host will not re-schedule a managed thread to a different native thread between these calls. Your code needs to run at a high trust level to call these methods, though.

Anton Tykhyy
+1 for highlighting the risks of the above approach
Odrade
yes, good point !
Thomas Levesque
Thanks for the update. If I could upvote you again, I would.
Odrade