NetTcpBinding 和 async/await WCF 阻塞
NetTcpBinding and async/await WCF blocking
我们正在创建一个共享 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()
。
似乎无法在共享会话上获得正确的并发响应。
编辑
我已经添加了我认为正在发生的事情的图像,这 只发生在缓冲模式下 ,在流模式下主线程不会阻塞。
@Underscore
我最近试图解决完全相同的问题。虽然,在使用它的线程被释放之前,我无法确定 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()
也有效。
如果能真正理解为什么会发生这种情况,那就太好了。
我们正在创建一个共享 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()
。
似乎无法在共享会话上获得正确的并发响应。
编辑
我已经添加了我认为正在发生的事情的图像,这 只发生在缓冲模式下 ,在流模式下主线程不会阻塞。
@Underscore
我最近试图解决完全相同的问题。虽然,在使用它的线程被释放之前,我无法确定 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()
也有效。
如果能真正理解为什么会发生这种情况,那就太好了。