NetTcpBinding 和 async/await WCF 阻塞

Posted

技术标签:

【中文标题】NetTcpBinding 和 async/await WCF 阻塞【英文标题】:NetTcpBinding and async/await WCF blocking 【发布时间】:2018-07-24 03:49:48 【问题描述】:

我们正在创建一个共享 WCF 通道以用于异步操作:

var channelFactory = new ChannelFactory<IWcfService>(new NetTcpBinding TransferMode = TransferMode.Buffered);

channelFactory.Endpoint.Behaviors.Add(new DispatcherSynchronizationBehavior(true, 25));
var channel = channelFactory.CreateChannel(new EndpointAddress(new Uri("net.tcp://localhost:80/Service").AbsoluteUri + "/Test"));

这会调用以下服务:

[ServiceContract]
public interface IWcfService

    [OperationContract]
    Task<MyClass> DoSomethingAsync();


[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.PerCall)]
public class WcfServiceImpl : IWcfService

    public Task<MyClass> DoSomethingAsync()
    
        Thread.Sleep(4000);

        return Task.FromResult(new MyClass());
    


[Serializable]
public class MyClass

    public string SomeString  get; set;       
    public MyClass Related  get; set;         
    public int[] Numbers  get; set; 

如果我们一次启动 3 个请求并在响应上模拟一个长时间运行的任务:

        using ((IDisposable)channel)
        
            var task1 = Task.Run(async () => await DoStuffAsync(channel));
            var task2 = Task.Run(async () => await DoStuffAsync(channel));
            var task3 = Task.Run(async () => await DoStuffAsync(channel));

            Task.WaitAll(task1, task2, task3);
        
    

    public static async Task DoStuffAsync(IWcfService channel)
    
        await channel.DoSomethingAsync();

        Console.WriteLine("Response");

        // Simulate long running CPU bound operation
        Thread.Sleep(5000);

        Console.WriteLine("Wait completed");
    

然后所有 3 个请求同时到达服务器,然后它同时响应所有 3 个请求。

但是,一旦响应到达客户端,它就会依次处理每个响应。

Response
// 5 second delay
Wait completed
// Instant
Response
// 5 second delay
Wait completed
// Instant
Response

响应在不同的线程上恢复,但每次只运行 1 个。

如果我们使用流而不是缓冲,我们会得到预期的行为,客户端会同时处理所有 3 个响应。

我们尝试设置最大缓冲区大小,使用DispatcherSynchronizationBehaviour,不同的并发模式,切换会话,ConfigureAwait false 和显式调用channel.Open()

似乎无法在共享会话上获得正确的并发响应。

编辑

我添加了我认为正在发生的事情的图像,这仅在缓冲模式下发生,在流模式下主线程不会阻塞。

【问题讨论】:

首先,你为什么使用阻塞Thread.Sleep(5000)而不是非阻塞await Task.Delay(5000)?其次,你为什么使用Task.Run 来做异步方法?在深入挖掘之前,您应该首先正确应用 async/await 模式。 Thread.Sleep只是模拟一个长时间的阻塞操作,实际上是几万行代码需要几分钟才能完成,这只是提供一个MVCE的代码。如上,Task.Run 在 Console Main 方法中提供了一个可运行且易于验证的示例。 是的,但是在Task DoStuffAsync(IWcfService channel) 中这样做会阻塞客户端的线程,请参阅我的回答。 【参考方案1】:

@下划线

我最近试图解决完全相同的问题。虽然,在使用它的线程被释放之前,我无法准确确定为什么TransferMode.Buffered 会导致 WCF 通道上的全局锁定,但我发现了类似的问题deadlock after awaiting。他们建议了一种解决方法,即将RunContinuationsAsynchronously() 添加到您的等待中,即await channel.DoSomethingAsync().RunContinuationsAsynchronously() 其中RunContinuationsAsynchronously()

public static class TaskExtensions

    public static Task<T> RunContinuationsAsynchronously<T>(this Task<T> task)
    
        var tcs = new TaskCompletionSource<T>();

        task.ContinueWith((t, o) =>
        
            if (t.IsFaulted)
            
                if (t.Exception != null) tcs.SetException(t.Exception.InnerExceptions);
            
            else if (t.IsCanceled)
            
                tcs.SetCanceled();
            
            else
            
                tcs.SetResult(t.Result);
            
        , TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);

        return tcs.Task;
    

    public static Task RunContinuationsAsynchronously(this Task task)
    
        var tcs = new TaskCompletionSource<object>();

        task.ContinueWith((t, o) =>
        
            if (t.IsFaulted)
            
                if (t.Exception != null) tcs.SetException(t.Exception.InnerExceptions);
            
            else if (t.IsCanceled)
            
                tcs.SetCanceled();
            
            else
            
                tcs.SetResult(null);
            
        , TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);

        return tcs.Task;
    

分隔 WCF 延续。显然Task.Yield() 也可以。

如果能真正理解为什么会发生这种情况会很好。

【讨论】:

以上是关于NetTcpBinding 和 async/await WCF 阻塞的主要内容,如果未能解决你的问题,请参考以下文章

NetTcpBinding 和 async/await WCF 阻塞

netTcpBinding 最快的安全配置是啥?

NetTcpBinding(读取 WindowsStreamSecurityBindingElement)如何加密/签署消息?

Azure:使用完整 IIS 的内部 WebRole 通信 (netTcpBinding)

带有 netTcpBinding 的 WCF 路由服务

WCF - 如何为 NTLM 身份验证配置 netTcpBinding?