起因:wcf的I/O方法没有并发的执行。(特性设置:[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Multiple)])
我写了一个模拟该操作的方法:
private static void GetCurrent()
{
string name = "abc";
//DoWork(name);
Thread[] t = new Thread[_threadCount];
for (int i = 0; i < t.Length; i++)
{
t[i] = new Thread(() => DoWork(name));
t[i].Start();
}
for (int i = 0; i < t.Length; i++)
{
t[i].Join();
}
}
DoWork调用wcf的方法,但是方法没有并发执行,而是顺序执行的。偶然地,我先调用了一次DoWork(name);发现可以并发执行了。另外,我又发现我没有调用client.Open()方法。于是我先调用client.Open(),这时候不调用DoWork(name);也可以并发执行。由此推断,问题在于我的代理没有打开,导致了不能并发执行。
网上找的解释:
If you use ChannelFactory<T> to create a channel, or new a svcutil-generated client, the top-most layer of the channel stack is ServiceChannel.The interesting implementation detail enters because ServiceChannel both supports auto-open and does not know whether its user cares about Message ordering.Let's look at how auto-open could work. The simplest implementation would just check this.State and call Open if it is Created. This does not work for multiple threads though, because it may result in multiple Open calls.(抄自:http://blogs.msdn.com/b/mjm/archive/2006/11/13/auto-open-part-1.aspx)
The best practice in this case is that: you should always open WCF client proxy explicitly before you are making any calls. Here is the sample code if you use auto-generated proxy from svcutil.exe:
MyHelloServiceClient proxy = new MyHelloServiceClient();
proxy.Open();
// Make a call with the proxy
proxy.Hello("Hello world!");
Here is the sample code if you use ChannelFactory<T> to create a proxy:
ISimpleContract proxy = factory.CreateChannel();
((IClientChannel)proxy).Open();
// Make a call with the proxy
proxy.Hello("Hello world!");
If you don’t call the “Open” method first, the proxy would be opened internally when the first call is made on the proxy. This is called auto-open.
Why? When the first message is sent through the auto-opened proxy, it will cause the proxy to be opened automatically. You can use .NET Reflector to open the method System.ServiceModel.Channels.ServiceChannel.Call and see the following code:
if (!this.explicitlyOpened)
{
this.EnsureDisplayUI();
this.EnsureOpened(rpc.TimeoutHelper.RemainingTime());
}
When you drill down into EnsureOpened, you will see that it calls CallOnceManager.CallOnce. For non-first calls, you would hit SyncWait.Wait which waits for the first request to complete. This mechanism is to ensure that all requests wait for the proxy to be opened and it also ensures the correct execution order. Thus all requests are serialized into a single execution sequence until all requests are drained out from the queue. This is not a desired behavior in most cases.
To avoid such “serializing” artifact, the best practice is to open the proxy explicitly as above. Once you get to this point, you will be able to share the same proxy object among multiple threads.
(抄自:http://blogs.msdn.com/b/wenlong/archive/2007/10/26/best-practice-always-open-wcf-client-proxy-explicitly-when-it-is-shared.aspx)
更多思考:
msdn上给了两个示例,一个是调用Open方法的,另一个没有调用,区别是另一个的TChannel是IRequestChannel
BasicHttpBinding myBinding = new BasicHttpBinding();
EndpointAddress myEndpoint = new EndpointAddress("http://localhost/MathService/Ep1");
ChannelFactory<IMath> myChannelFactory = new ChannelFactory<IMath>(myBinding, myEndpoint);
// Create a channel.
IMath wcfClient1 = myChannelFactory.CreateChannel();
double s = wcfClient1.Add(3, 39);
Console.WriteLine(s.ToString());
((IClientChannel)wcfClient1).Close();
msdn原文:http://msdn.microsoft.com/en-us/library/ms734681(v=vs.110).aspx
BasicHttpBinding binding = new BasicHttpBinding();
EndpointAddress address = new EndpointAddress("http://localhost:8000/ChannelApp");
ChannelFactory<IRequestChannel> factory =
new ChannelFactory<IRequestChannel>(binding, address);
IRequestChannel channel = factory.CreateChannel();
channel.Open();
Message request = Message.CreateMessage(MessageVersion.Soap11, "hello");
Message reply = channel.Request(request);
Console.Out.WriteLine(reply.Headers.Action);
reply.Close();
channel.Close();
factory.Close();
msdn原文:http://msdn.microsoft.com/en-us/library/ms576132(v=vs.110).aspx
TChannel是IRequestChannel,调用Open()方法,也调用了Close()方法。
难道是当TChannel是IMath契约的时候,通道已经打开?
那么再次调用Open方法会出错。做个测试,调用Open方法两次:
调用((IClientChannel)wcfClient1).Open(); 一次没有问题,但连续调用两次就出错了。“System.InvalidOperationException : 通信对象 System.ServiceModel.Channels.ServiceChannel 处于 Opened 状态时无法对其进行修改。”
msdn对Open的解释如下:
When an ICommunicationObject is instantiated, it begins in the Created state. In the Created state, the object can be configured (for example, properties can be set, or events can be registered), but it is not yet usable to send or receive messages. The Open method causes an ICommunicationObject to enter into the Opening state where it remains until the open operation succeeds, the open operation times out or fails and the object becomes faulted, or the ICommunicationObject is aborted.
In the Opened state, the ICommunicationObject is usable (for example, messages can be received), but it is no longer configurable.
可以看到,要调用I/O方法,必须确保Channel是Opened状态。
总结,我认为IMath wcfClient1 = myChannelFactory.CreateChannel();这种方式可以不调用Open方法,wcf帮忙做了这个工作。MyHelloServiceClient proxy = new MyHelloServiceClient();这种方式一点要用Open方法。
另外,我看到有人这样关闭通道
(wcfClient1 as ICommunicationObject).Close();
但是示例代码是这样的:
((IClientChannel)wcfClient1).Close();
确实,Close()是在ICommunicationObject接口定义的,但是用as对不对呢?应该用Cast呢还是as呢?
msdn说“The as operator is like a cast operation. However, if the conversion is not possible, as returns null instead of raising an exception. ”
expression as type
等价于
expression is type ? (type)expression : (type)null
那么,在这里用((IClientChannel)wcfClient1).Close();应该更安全。