【问题标题】:Does calling asynchronous Task based WCF method utilize the I/O completion port or a Thread Pool thread to call the continuation?调用基于异步任务的 WCF 方法是否利用 I/O 完成端口或线程池线程来调用延续?
【发布时间】:2023-03-26 11:34:01
【问题描述】:

我有以下 WCF 合同:

[ServiceContract(Namespace = "http://abc/Services/AdminService")]
public interface IAdminService
{
    [OperationContract]
    string GetServiceVersion();

    // More methods here
}

GetServiceVersion 是一个返回字符串的简单方法。它用作 ping 以检查服务是否可达。

现在我想异步调用它,认为它比使用 .NET 线程在后台调用它更有效。

所以,我专门为此设计了以下界面:

[ServiceContract(Namespace = "http://abc/Services/AdminService")]
public interface IMiniAdminService
{
    [OperationContract(Action = "http://abc/Services/AdminService/IAdminService/GetServiceVersion", ReplyAction = "http://abc/Services/AdminService/IAdminService/GetServiceVersionResponse")]
    Task<string> GetServiceVersionAsync();
}

这使得异步调用GetServiceVersion API 成为可能:

var tmp = new ChannelFactory<IAdminService>("AdminServiceClientEndpoint");
var channelFactory = new ChannelFactory<IMiniAdminService>(tmp.Endpoint.Binding, tmp.Endpoint.Address);
var miniAdminService = channelFactory.CreateChannel();
return miniAdminService.GetServiceVersionAsync().ContinueWith(t =>
{
    if (t.Exception != null)
    {
        // The Admin Service seems to be unavailable
    }
    else
    {
        // The Admin Service is available
    }
});

代码有效。

我的问题是 - 它是否利用 IOCP 来调用延续?

一般来说,有没有办法知道是否通过 IOCP 调用了延续(如果需要,在调试器中)?

附言

这是我的异步 WCF 方法延续的堆栈跟踪:

>   *** My Code *** Line 195    C#
    mscorlib.dll!System.Threading.Tasks.ContinuationTaskFromResultTask<string>.InnerInvoke() + 0x111 bytes  
    mscorlib.dll!System.Threading.Tasks.Task.Execute() + 0x69 bytes 
    mscorlib.dll!System.Threading.Tasks.Task.ExecutionContextCallback(object obj) + 0x4f bytes  
    mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) + 0x28d bytes 
    mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) + 0x47 bytes  
    mscorlib.dll!System.Threading.Tasks.Task.ExecuteWithThreadLocal(ref System.Threading.Tasks.Task currentTaskSlot) + 0x3b5 bytes  
    mscorlib.dll!System.Threading.Tasks.Task.ExecuteEntry(bool bPreventDoubleExecution) + 0x104 bytes   
    mscorlib.dll!System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() + 0x2a bytes    
    mscorlib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch() + 0x249 bytes  
    mscorlib.dll!System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() + 0x1e bytes    
    [Native to Managed Transition]  

现在,这个堆栈跟踪看起来与我从Task.Factory.StartNew 调用的方法得到的非常相似,这确实是基于线程池的:

>   *** My Code *** Line 35 C#
    mscorlib.dll!System.Threading.Tasks.Task<int>.InnerInvoke() + 0x59 bytes    
    mscorlib.dll!System.Threading.Tasks.Task.Execute() + 0x60 bytes 
    mscorlib.dll!System.Threading.Tasks.Task.ExecutionContextCallback(object obj) + 0x37 bytes  
    mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) + 0x1a2 bytes 
    mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) + 0x33 bytes  
    mscorlib.dll!System.Threading.Tasks.Task.ExecuteWithThreadLocal(ref System.Threading.Tasks.Task currentTaskSlot) + 0x2ff bytes  
    mscorlib.dll!System.Threading.Tasks.Task.ExecuteEntry(bool bPreventDoubleExecution) + 0xd3 bytes    
    mscorlib.dll!System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() + 0x22 bytes    
    mscorlib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch() + 0x22e bytes  
    mscorlib.dll!System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() + 0x18 bytes    
    [Native to Managed Transition]  

【问题讨论】:

  • 请注意,您不必修改服务器以使客户端异步(并使用 IOCP)。这与问题无关,只是说。并且使用Task.Factory.StartNew 肯定不会获得 IOCP,而是会增加开销。
  • 当然,但是有没有办法告诉 IOCP 驱动的延续与线程池驱动的延续?因为,在 continuation 中的堆栈跟踪看起来几乎相同。
  • 我感觉延续已发布到线程池,因此它没有在前一个任务运行的线程上同步运行。尝试添加ExecuteSynchronously 并再次查看堆栈。我 99% 是肯定的,IOCP 被用于 .NET 中的所有相关网络内容。见stackoverflow.com/a/21439483/122718

标签: c# wcf asynchronous task-parallel-library


【解决方案1】:

首先,您需要添加 TaskContinuationOptions.ExecuteSynchronously,以确保在异步 IO 操作已完成的同一线程上调用延续回调:

return miniAdminService.GetServiceVersionAsync().ContinueWith(t =>
{
    if (t.Exception != null)
    {
        // The Admin Service seems to be unavailable
    }
    else
    {
        // The Admin Service is available
    }
}, TaskContinuationOptions.ExecuteSynchronously);

显然,.NET 中没有 API 来判断线程是否是 IOCP 池线程。你只能判断线程是否是线程池线程(Thread.CurrentThread.IsThreadPoolThread),对于 IOCP 线程也是true

在 Win32 中,使用 CreateIoCompletionPort API 创建了一个 IOCP 线程池,但我也找不到用于检查线程是否属于该池的 Win32 API。

所以,这里有一个有点人为的例子来在实践中检查这个理论,使用HtppClient 作为测试工具。首先,我们确保所有非 IOCP 线程都使用-1 填充了ThreadStatic 变量s_mark。然后我们启动一个 IO-bound 操作并在 IO-bound 操作完成的线程上检查s_mark

using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication_22465346
{
    public class Program
    {
        [ThreadStatic]
        static volatile int s_mark;

        // Main
        public static void Main(string[] args)
        {
            const int THREADS = 50;

            // init the thread pool
            ThreadPool.SetMaxThreads(
                workerThreads: THREADS, completionPortThreads: THREADS);
            ThreadPool.SetMinThreads(
                workerThreads: THREADS, completionPortThreads: THREADS);

            // populate s_max for non-IOCP threads
            for (int i = 0; i < THREADS; i++)
            {
                ThreadPool.QueueUserWorkItem(_ =>
                { 
                    s_mark = -1;
                    Thread.Sleep(1000);
                });
            }
            Thread.Sleep(2000);

            // non-IOCP test
            Task.Run(() =>
            {
                // by now all non-IOCP threads have s_mark == -1
                Console.WriteLine("Task.Run, s_mark: " + s_mark);
                Console.WriteLine("IsThreadPoolThread: " + Thread.CurrentThread.IsThreadPoolThread);
            }).Wait();

            // IOCP test
            var httpClient = new HttpClient();
            httpClient.GetStringAsync("http://example.com").ContinueWith(t =>
            {
                // all IOCP threads have s_mark == 0
                Console.WriteLine("GetStringAsync.ContinueWith, s_mark: " + s_mark);
                Console.WriteLine("IsThreadPoolThread: " + Thread.CurrentThread.IsThreadPoolThread);
            }, TaskContinuationOptions.ExecuteSynchronously).Wait();

            Console.WriteLine("Enter to exit...");
            Console.ReadLine();
        }
    }
}

输出:

任务运行,s_mark:-1 IsThreadPoolThread: 真 GetStringAsync.ContinueWith, s_mark: 0 IsThreadPoolThread: 真 输入退出...

我认为这可能足以证实 IO-bound continuation确实发生在 IOCP 线程上的理论。

一本好书,相关:"There Is No Thread",作者 Stephen Cleary。

【讨论】:

  • 优秀的答案。谢谢。
猜你喜欢
  • 1970-01-01
  • 2020-01-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-04
  • 1970-01-01
  • 1970-01-01
  • 2011-01-30
相关资源
最近更新 更多