客户端连接到WCF服务时观察超时异常 - 两者都驻留在同一个应用程序中

时间:2014-05-20 12:45:49

标签: c# .net wcf

我有一个托管WCF服务的应用。一些信息:

  • 它必须是一个单独的应用程序 - 意味着它的另一个实例不能并行运行。现在,假设下面的示例表单应用程序已经设计好了。
  • 它也需要充当客户。
  • 它使用net.tcp binding
  • 它使用SecurityMode = Transport

但是当我从客户端调用方法时,服务器没有得到调用。我看到了#34; Timeout异常"当" CallServer"方法被调用。

如果您建议更改InstanceContextModeConcurrencyModeSessionMode的设置,请先自行尝试,因为我已经尝试了这些组合但没有帮助。

这里的代码是一个示例,但可用于测试。要使用此代码,请在表单上创建2个按钮:btnHostService和btnClientInvoke。

using System;
using System.ServiceModel;
using System.Windows.Forms;

namespace WindowsFormsApplication1 {
    public partial class Form1 : Form {
        ServiceHost _host;
        const string URI = "net.tcp://localhost:8602/MyService";

        public Form1() {
            InitializeComponent();
            this.FormClosing += delegate { if (_host != null) _host.Close(new TimeSpan(0, 1, 0)); };
        }

        private void btnHostService_Click(object sender, EventArgs e) {
            //don't host again
            if (_host != null) return;

            _host = new ServiceHost(typeof(ContractServer), new Uri(URI));

            var binding = new NetTcpBinding(SecurityMode.Transport) { PortSharingEnabled = true };
            binding.Security.Transport.ClientCredentialType = TcpClientCredentialType.Windows;

            _host.AddServiceEndpoint(typeof(IContract), binding, string.Empty);
            _host.Opened += delegate { MessageBox.Show(this, "Service Hosted"); };
            _host.Open();
        }

        private void btnClientInvoke_Click(object sender, EventArgs e) {
            var binding = new NetTcpBinding(SecurityMode.Transport) { PortSharingEnabled = true };
            binding.Security.Transport.ClientCredentialType = TcpClientCredentialType.Windows;

            ////set all of them to 1 min - which is default
            //binding.OpenTimeout = new TimeSpan(0, 1, 0);
            //binding.SendTimeout = new TimeSpan(0, 1, 0);
            //binding.CloseTimeout = new TimeSpan(0, 1, 0);

            ContractClient client = null;
            try {
                client = new ContractClient(binding, new EndpointAddress(URI));
                client.CallServer();
            }
            catch (Exception exc) {
                MessageBox.Show(exc.ToString());
            }

            if (client != null) {
                try {
                    client.Close();
                }
                catch (Exception exc) {
                    MessageBox.Show(exc.ToString());
                    client.Abort();
                }
            }
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e) {
            if (_host != null)
                _host.Close(new TimeSpan(0, 1, 0));
        }
    }

    [ServiceContract(SessionMode = SessionMode.Allowed)]
    public interface IContract {
        [OperationContract(IsOneWay = true)]
        void CallServer();
    }

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple, IncludeExceptionDetailInFaults = true)]
    public class ContractServer : IContract {
        public void CallServer() {
            MessageBox.Show("Client called!");
        }
    }
    public class ContractClient : System.ServiceModel.ClientBase<IContract>, IContract {
        public ContractClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) : base(binding, remoteAddress) { }

        public void CallServer() {
            base.Channel.CallServer();
        }
    }
}

更新1

有关信息:修复是使用&#34; UseSynchronizationContext = false&#34;在ContractServer中并禁用主机绑定中的端口共享。但我不知道为什么。

1 个答案:

答案 0 :(得分:2)

你可能陷入僵局。

当您在应用程序中托管WCF服务时,它会使用该应用程序的同步上下文。在这种情况下,WinForms应用程序,单线程同步上下文。

所以当你的&#34;客户&#34;打电话给你的服务器&#34;它被阻止,直到它得到响应和&#34;服务器&#34;无法发送该响应,因为单个线程被&#34;客户端&#34;阻止,因此死锁。

要解决此问题,您需要告诉服务不要使用该同步上下文:

[ServiceBehavior(
    UseSynchronizationContext = false,
    InstanceContextMode = InstanceContextMode.Single, 
    ConcurrencyMode = ConcurrencyMode.Multiple, 
    IncludeExceptionDetailInFaults = true)]
public class ContractServer : IContract 
{
    public void CallServer() 
    {
        MessageBox.Show("Client called!");
    }
}