1

I'm try to understand iocp and io completion threadpool. For all I know, IO operate like http request via iocp and io completion thread to execute callback code. But I found some difference between File read, Http request and SQL query.

class Program
{
    static void Main(string[] args)
    {
        FileAsync().Wait();
        PrintSeparate();
        SqlAsync().Wait();
        PrintSeparate();
        HttpAsync().Wait();
        Console.ReadKey();
    }

    private static async Task FileAsync()
    {
        PrintThreads($"{nameof(FileAsync)}-Entry             ");
        var buffer = new byte[1024];
        using (var file = new FileStream(@"D:\a.txt", FileMode.Open, FileAccess.Read, FileShare.Read, 1024, FileOptions.Asynchronous))
        {
            PrintThreads($"{nameof(FileAsync)}-AfterNewFileStream");
            await file.ReadAsync(buffer, 0, 1024);
            PrintThreads($"{nameof(FileAsync)}-AfterReadAsync    ");
            PrintFirstStack();
        }
    }

    private static async Task HttpAsync()
    {
        PrintThreads($"{nameof(HttpAsync)}-Entry         ");
        var httpClient = new HttpClient();
        var response = await httpClient.GetAsync(@"https://stackoverflow.com");
        PrintThreads($"{nameof(HttpAsync)}-AfterGetAsync ");
        PrintFirstStack();
        await response.Content.ReadAsStringAsync();
        PrintThreads($"{nameof(HttpAsync)}-AfterReadAsync");
        PrintFirstStack();
    }

    private static async Task SqlAsync()
    {
        PrintThreads($"{nameof(SqlAsync)}-Entry            ");
        using (var connection = new SqlConnection("Data Source=***;Initial Catalog=t;User ID=sa; Password=***"))
        using (var command = new SqlCommand("select count(1) from aa", connection))
        {
            PrintThreads($"{nameof(SqlAsync)}-AfterNewConCmd   ");
            await connection.OpenAsync();
            PrintThreads($"{nameof(SqlAsync)}-AfterOpenAsync   ");
            PrintFirstStack();
            var o = await command.ExecuteScalarAsync();
            PrintThreads($"{nameof(SqlAsync)}-AfterExecuteAsync");
            PrintFirstStack();
        }
    }

    private static void PrintThreads(object flag)
    {
        ThreadPool.GetAvailableThreads(out var workThreads, out var ioThreads);
        PrintThreads(flag, workThreads, ioThreads);
    }

    private static void PrintThreads(object flag, int workThreads, int ioThreads)
    {
        Console.WriteLine($"[{flag}] {nameof(workThreads)}: {workThreads}, {nameof(ioThreads)}: {ioThreads}");
    }

    private static void PrintSeparate()
    {
        Console.WriteLine("\n-------------------------------------------------------------------------------\n");
    }

    private static void PrintFirstStack()
    {
        Console.WriteLine(Environment.StackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries).Last());
    }
}

Output:

[FileAsync-Entry             ] workThreads: 2047, ioThreads: 1000
[FileAsync-AfterNewFileStream] workThreads: 2047, ioThreads: 999
[FileAsync-AfterReadAsync    ] workThreads: 2046, ioThreads: 1000
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()

-------------------------------------------------------------------------------

[SqlAsync-Entry            ] workThreads: 2047, ioThreads: 1000
[SqlAsync-AfterNewConCmd   ] workThreads: 2047, ioThreads: 1000
[SqlAsync-AfterOpenAsync   ] workThreads: 2046, ioThreads: 1000
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
[SqlAsync-AfterExecuteAsync] workThreads: 2045, ioThreads: 1000
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()

-------------------------------------------------------------------------------

[HttpAsync-Entry         ] workThreads: 2047, ioThreads: 1000
[HttpAsync-AfterGetAsync ] workThreads: 2047, ioThreads: 999
   at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)
[HttpAsync-AfterReadAsync] workThreads: 2047, ioThreads: 999
   at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)

The output show me, only http request using ioThread to execute callback, why? Is file and sql using ioThread insdie framework code, and then enqueue user code callback to workthreadpool?

And I had test multi-times, new FileStream(..) always taken a ioThread, what's happened?

Hourglass
  • 150
  • 1
  • 10
  • 2
    A fundamental feature of async I/O is that it doesn't necessarily have to complete asynchronously. It won't when the driver happens to have the data available when it gets started. Very likely with a file, the file system cache aggressively pre-caches file data. Pretty unlikely to happen with http, the Internet is quite slow so easy to pull data faster than the driver can buffer. You can't really guess how sql is going to behave, but likely to be async right after the query gets started. You can never really guess. – Hans Passant Sep 17 '19 at 06:52
  • @HansPassant I changed sql to `WAITFOR DELAY '00:00:10';select count(1) from aa;`, but it was still use workthread. I'm really baffled by this. – Hourglass Sep 17 '19 at 07:24
  • You can't assume that I/O requests for SQL queries are performed directly by .NET code. There is always a provider involved, supplied by the dbase manufacturer and normally written in C. Which is likely to use IOCP, but you can't see that since it buried in unmanaged code. The framework uses APM, so completion is done on a worker thread instead of an I/O thread. – Hans Passant Sep 17 '19 at 07:50
  • 1
    @HansPassant but why use worker thread instead of IO thread, [both of them are CLR threadpool thread](https://stackoverflow.com/a/2168740/3614672). you said that file may has pre-caches, ok, I understand, just like `await Task.Delay()`, there is no actual IO operate. When does I/O callback use I/O thread and when does I/O callback use work thread? Is that you mean only bind iocp directly in .NET code will use IO thread, and SQL may bind iocp in C library, so .net use work thread instead of IO thread? thanks for your reply. – Hourglass Sep 17 '19 at 09:04
  • https://referencesource.microsoft.com/#mscorlib/system/threading/threadpool.cs,ad6561b66443aacc,references – Hans Passant Sep 17 '19 at 09:31
  • This is a great question. Would love to see it answered – Rob L May 24 '22 at 16:17

0 Answers0