调用基于异步任务的 WCF 方法是否利用 I/O 完成端口或线程池线程来调用延续? [英] Does calling asynchronous Task based WCF method utilize the I/O completion port or a Thread Pool thread to call the continuation?

查看:26
本文介绍了调用基于异步任务的 WCF 方法是否利用 I/O 完成端口或线程池线程来调用延续?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下 WCF 合同:

[ServiceContract(Namespace = "http://abc/Services/AdminService")]公共接口 IAdminService{【经营合同】字符串 GetServiceVersion();//这里有更多方法}

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

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

因此,我为此目的设计了以下界面:

[ServiceContract(Namespace = "http://abc/Services/AdminService")]公共接口 IMiniAdminService{[OperationContract(Action = "http://abc/Services/AdminService/IAdminService/GetServiceVersion", ReplyAction = "http://abc/Services/AdminService/IAdminService/GetServiceVersionResponse")]任务<字符串>GetServiceVersionAsync();}

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

var tmp = new ChannelFactory("AdminServiceClientEndpoint");var channelFactory = new ChannelFactory(tmp.Endpoint.Binding, tmp.Endpoint.Address);var miniAdminService = channelFactory.CreateChannel();返回 miniAdminService.GetServiceVersionAsync().ContinueWith(t =>{如果(t.Exception != null){//管理服务似乎不可用}别的{//管理服务可用}});

代码有效.

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

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

附言

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

>*** 我的代码 *** 第 195 行 C#mscorlib.dll!System.Threading.Tasks.ContinuationTaskFromResultTask.InnerInvoke() + 0x111 字节mscorlib.dll!System.Threading.Tasks.Task.Execute() + 0x69 字节mscorlib.dll!System.Threading.Tasks.Task.ExecutionContextCallback(object obj) + 0x4f 字节mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback 回调, 对象状态, bool preserveSyncCtx) + 0x28d 字节mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback 回调, 对象状态, bool preserveSyncCtx) + 0x47 字节mscorlib.dll!System.Threading.Tasks.Task.ExecuteWithThreadLocal(ref System.Threading.Tasks.Task currentTaskSlot) + 0x3b5 字节mscorlib.dll!System.Threading.Tasks.Task.ExecuteEntry(bool bPreventDoubleExecution) + 0x104 字节mscorlib.dll!System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() + 0x2a 字节mscorlib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch() + 0x249 字节mscorlib.dll!System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() + 0x1e 字节[本机到托管过渡]

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

>*** 我的代码 *** 第 35 行 C#mscorlib.dll!System.Threading.Tasks.Task.InnerInvoke() + 0x59 字节mscorlib.dll!System.Threading.Tasks.Task.Execute() + 0x60 字节mscorlib.dll!System.Threading.Tasks.Task.ExecutionContextCallback(object obj) + 0x37 字节mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback 回调, 对象状态, bool preserveSyncCtx) + 0x1a2 字节mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback 回调, 对象状态, bool preserveSyncCtx) + 0x33 字节mscorlib.dll!System.Threading.Tasks.Task.ExecuteWithThreadLocal(ref System.Threading.Tasks.Task currentTaskSlot) + 0x2ff 字节mscorlib.dll!System.Threading.Tasks.Task.ExecuteEntry(bool bPreventDoubleExecution) + 0xd3 字节mscorlib.dll!System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() + 0x22 字节mscorlib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch() + 0x22e 字节mscorlib.dll!System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() + 0x18 字节[本机到托管过渡]

解决方案

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

return miniAdminService.GetServiceVersionAsync().ContinueWith(t =>{如果(t.Exception != null){//管理服务似乎不可用}别的{//管理服务可用}}, TaskContinuationOptions.ExecuteSynchronously);

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

在 Win32 中,使用 CreateIoCompletionPort API,但我也找不到 Win32 API 来检查线程是否属于此类池.

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

使用系统;使用 System.Net.Http;使用 System.Threading;使用 System.Threading.Tasks;命名空间 ConsoleApplication_22465346{公开课计划{[线程静态]静态易失性 int s_mark;//主要的public static void Main(string[] args){const int 线程 = 50;//初始化线程池ThreadPool.SetMaxThreads(workerThreads: THREADS, completionPortThreads: THREADS);ThreadPool.SetMinThreads(workerThreads: THREADS, completionPortThreads: THREADS);//为非 IOCP 线程填充 s_maxfor (int i = 0; i < THREADS; i++){ThreadPool.QueueUserWorkItem(_ =>{s_mark = -1;线程睡眠(1000);});}线程睡眠(2000);//非 IOCP 测试Task.Run(() =>{//现在所有非 IOCP 线程都有 s_mark == -1Console.WriteLine("Task.Run, s_mark:" + s_mark);Console.WriteLine("IsThreadPoolThread:" + Thread.CurrentThread.IsThreadPoolThread);}).等待();//IOCP 测试var httpClient = new HttpClient();httpClient.GetStringAsync("http://example.com").ContinueWith(t =>{//所有 IOCP 线程都有 s_mark == 0Console.WriteLine("GetStringAsync.ContinueWith, s_mark:" + s_mark);Console.WriteLine("IsThreadPoolThread:" + Thread.CurrentThread.IsThreadPoolThread);}, TaskContinuationOptions.ExecuteSynchronously).Wait();Console.WriteLine("回车退出...");Console.ReadLine();}}}

输出:

<前>Task.Run,​​s_mark:-1IsThreadPoolThread: 真GetStringAsync.ContinueWith, s_mark: 0IsThreadPoolThread: 真输入退出...

我认为这可能足以证实 IO 绑定的延续 确实 发生在 IOCP 线程上的理论.

一本好书,相关:没有线程"斯蒂芬·克利里.

I have the following WCF contract:

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

    // More methods here
}

The GetServiceVersion is a simple method returning some string. It is used as a ping to check whether the service is reachable.

Now I would like to call it asynchronously, thinking it would be more efficient than using .NET threads to call it in background.

So, I have come up with the following interface just for that purpose:

[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();
}

This makes it possible to invoke the GetServiceVersion API asynchronously:

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
    }
});

The code works.

My question is this - does it utilize the IOCP to invoke the continuation?

In general, is there a way to know whether a continuation is invoked through IOCP or not (in the debugger, if needed) ?

P.S.

Here is the stack trace of my async WCF method continuation:

>   *** 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]  

Now, this stack trace looks very similar to the one I get for a method called from Task.Factory.StartNew, which is indeed Thread Pool based:

>   *** 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]  

解决方案

First, you'd need to add TaskContinuationOptions.ExecuteSynchronously, to make sure the continuation callback is called on the same thread the async IO operation has been finalized:

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

Apparently, there is no API in .NET to tell if the thread is a IOCP pool thread. You can only tell if the thread is a thread pool thread (Thread.CurrentThread.IsThreadPoolThread), which is true for IOCP threads too.

In Win32, an IOCP thread pool is created with CreateIoCompletionPort API, but I couldn't find a Win32 API to check if the thread belongs to such pool, either.

So, here is a bit contrived example to check this theory in practice, using HtppClient as the test vehicle. First, we make sure all non-IOCP threads have populated the ThreadStatic variable s_mark with -1. Then we initiate an IO-bound operation and check s_mark on thread where the IO-bound operation gets completed:

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();
        }
    }
}

The output:

Task.Run, s_mark: -1
IsThreadPoolThread: True
GetStringAsync.ContinueWith, s_mark: 0
IsThreadPoolThread: True
Enter to exit...

I think this might be enough evidence to confirm the theory that an IO-bound continuation does happen on an IOCP thread.

A good read, related: "There Is No Thread" by Stephen Cleary.

这篇关于调用基于异步任务的 WCF 方法是否利用 I/O 完成端口或线程池线程来调用延续?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆