8

Creating a background thread in C# in the normal way -

Thread t = new Thread(....);
t.IsBackground = true;
t.Start();
etc etc

Wanting to call CancelSynchronousIO from the main thread to cancel a blocking IO call on the background thread. Don't know how to get a thread handle in the form of an IntPtr to pass to the function:

[DllImport("kernel32.dll", SetLastError=true)]
static extern bool CancelSynchronousIo(IntPtr threadHandle);

There seems to be various ways of getting a thread ID, but not a handle? And the ways of getting a thread ID seem to give you an ID only within the managed environment, so no use for PInvoke calls? I'm guessing I'm missing something.

Do I need to do other PInvoke calls to get the thread handle or is there an easier way?

Dmitry
  • 13,797
  • 6
  • 32
  • 48
user3757455
  • 81
  • 1
  • 3
  • your code might be running on a managed thread, not a native thread. – Daniel A. White Jul 02 '14 at 20:10
  • see: http://msdn.microsoft.com/en-us/library/74169f59(v=vs.110).aspx – Daniel A. White Jul 02 '14 at 20:11
  • 1
    @DanielA.White: Managed threads _are_ native threads; Fibers are almost never used. – SLaks Jul 02 '14 at 20:12
  • most .net io operations have a way to cancel. – Daniel A. White Jul 02 '14 at 20:12
  • 1
    @SLaks but it can be rescheduled else where: "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 Fiber API to schedule many managed threads against the same operating system thread, or to move a managed thread among different operating system threads." – Daniel A. White Jul 02 '14 at 20:12
  • 2
    Yes, but no-one does that. – SLaks Jul 02 '14 at 20:13
  • Built-in .NET IO is not cancellable. If some IO function takes a CToken it ignores it. – usr Nov 13 '14 at 22:09

1 Answers1

5

You can do it, but it is highly not recommended.

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;

class Program
{
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern uint GetCurrentThreadId();

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern IntPtr OpenThread(uint desiredAccess, bool inheritHandle, uint threadId);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool CloseHandle(IntPtr handle);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool CancelSynchronousIo(IntPtr threadHandle);

    static bool CancelSynchronousIo(uint threadId)
    {
        // GENERIC_WRITE, Non-inheritable
        var threadHandle = OpenThread(0x40000000, false, (uint)threadId);
        var ret = CancelSynchronousIo(threadHandle);

        CloseHandle(threadHandle);

        return ret;
    }

    static void Main(string[] args)
    {
        uint threadId = 0;

        using (var threadStarted = new AutoResetEvent(false))
        {
            var thread = new Thread(() =>
            {
                try
                {
                    Thread.BeginThreadAffinity();
                    threadId = GetCurrentThreadId();

                    threadStarted.Set();

                    // will throws System.OperationCanceledException
                    Console.ReadLine();
                }
                finally
                {
                    Thread.EndThreadAffinity();
                }
            });

            thread.Start();

            threadStarted.WaitOne();
        }

        Debugger.Break();

        CancelSynchronousIo(threadId);
    }
}
masaki
  • 121
  • 1
  • 6
  • 1
    Can you explain or reference why it isn't recommended? – jklemmack May 05 '17 at 19:56
  • I believe that you need to add `[return: MarshalAs(UnmanagedType.Bool)]` to `CancelSynchronousIo`. [See here](https://blogs.msdn.microsoft.com/jaredpar/2008/10/14/pinvoke-and-bool-or-should-i-say-bool/). – Paul Jun 21 '18 at 15:26
  • Also, it's documented that you need the `THREAD_TERMINATE` access right. I don't think that you can use `0x40000000` with `OpenThread`. – Paul Jun 21 '18 at 15:31